@oxy.so/contracts 4.21.0 → 4.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.
- package/dist/cjs/client/contentClaims.js +139 -0
- package/dist/cjs/client/index.js +21 -3
- package/dist/cjs/client/products.js +33 -11
- package/dist/cjs/contentClaims.js +246 -0
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/products/billing.js +1 -0
- package/dist/cjs/products/checkout.js +14 -1
- package/dist/cjs/products/plans.js +18 -2
- package/dist/cjs/userResponse.js +8 -1
- package/dist/esm/client/contentClaims.js +134 -0
- package/dist/esm/client/index.js +2 -1
- package/dist/esm/client/products.js +32 -10
- package/dist/esm/contentClaims.js +229 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/products/billing.js +1 -0
- package/dist/esm/products/checkout.js +13 -0
- package/dist/esm/products/plans.js +17 -1
- package/dist/esm/userResponse.js +8 -1
- package/dist/types/client/contentClaims.d.ts +106 -0
- package/dist/types/client/index.d.ts +2 -1
- package/dist/types/client/products.d.ts +43 -10
- package/dist/types/client/safety.d.ts +1 -1
- package/dist/types/contentClaims.d.ts +383 -0
- package/dist/types/email/messages.d.ts +2 -2
- package/dist/types/externalIdentity.d.ts +45 -12
- package/dist/types/index.d.ts +2 -1
- package/dist/types/inference/aliaModelRelease.d.ts +8 -8
- package/dist/types/inference/catalogue.d.ts +4 -4
- package/dist/types/inference/inbox.d.ts +4 -4
- package/dist/types/inference/modelDocumentation.d.ts +16 -16
- package/dist/types/products/billing.d.ts +8 -0
- package/dist/types/products/checkout.d.ts +36 -0
- package/dist/types/products/plans.d.ts +102 -5
- package/dist/types/safety.d.ts +77 -50
- package/dist/types/sessionStatus.d.ts +4 -4
- package/dist/types/updates.d.ts +6 -6
- package/dist/types/userResponse.d.ts +145 -9
- package/package.json +1 -1
package/dist/cjs/userResponse.js
CHANGED
|
@@ -246,6 +246,10 @@ exports.userResponseSchema = zod_1.z
|
|
|
246
246
|
* so it is present on the current-user DTO (`GET /users/me`,
|
|
247
247
|
* `GET /session/user/:sessionId`) and absent until the user sets it.
|
|
248
248
|
*/
|
|
249
|
+
userPreferences: zod_1.z
|
|
250
|
+
.object({ displayCurrency: zod_1.z.enum(['USD', 'EUR', 'FAIR']).optional() })
|
|
251
|
+
.catchall(zod_1.z.unknown())
|
|
252
|
+
.optional(),
|
|
249
253
|
themePreference: exports.themePreferenceSchema.optional(),
|
|
250
254
|
personalization: zod_1.z
|
|
251
255
|
.object({
|
|
@@ -306,7 +310,10 @@ exports.userProfileUpdateSchema = zod_1.z
|
|
|
306
310
|
languages: zod_1.z.array(zod_1.z.string()).optional(),
|
|
307
311
|
accountExpiresAfterInactivityDays: zod_1.z.number().nullable().optional(),
|
|
308
312
|
notificationPreferences: zod_1.z.record(zod_1.z.unknown()).optional(),
|
|
309
|
-
userPreferences: zod_1.z
|
|
313
|
+
userPreferences: zod_1.z
|
|
314
|
+
.object({ displayCurrency: zod_1.z.enum(['USD', 'EUR', 'FAIR']).optional() })
|
|
315
|
+
.catchall(zod_1.z.unknown())
|
|
316
|
+
.optional(),
|
|
310
317
|
privacySettings: zod_1.z.record(zod_1.z.unknown()).optional(),
|
|
311
318
|
/**
|
|
312
319
|
* Portable theme preference. Written through the same `PUT /users/me`
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content claims (ADR 0043 D10) — the constants of the public "Report content
|
|
3
|
+
* / rights claim" intake and of a claim's review, without Zod. Schemas:
|
|
4
|
+
* `../contentClaims.ts`.
|
|
5
|
+
*
|
|
6
|
+
* Part of `@oxy.so/contracts/client`: never import `zod` here (see `./check.ts`).
|
|
7
|
+
*/
|
|
8
|
+
/** What a claim asserts. */
|
|
9
|
+
export const CONTENT_CLAIM_KINDS = [
|
|
10
|
+
'copyright',
|
|
11
|
+
'trademark',
|
|
12
|
+
'privacy',
|
|
13
|
+
'illegal_content',
|
|
14
|
+
'other',
|
|
15
|
+
];
|
|
16
|
+
/** Who the claimant is to the content. */
|
|
17
|
+
export const CLAIMANT_RELATIONSHIPS = [
|
|
18
|
+
'rights_holder',
|
|
19
|
+
'authorized_agent',
|
|
20
|
+
'affected_person',
|
|
21
|
+
'other',
|
|
22
|
+
];
|
|
23
|
+
/** The bounds of a submission. The website fills its validation messages from these. */
|
|
24
|
+
export const CONTENT_CLAIM_LIMITS = {
|
|
25
|
+
minUrls: 1,
|
|
26
|
+
maxUrls: 20,
|
|
27
|
+
maxUrlLength: 2048,
|
|
28
|
+
minDescription: 20,
|
|
29
|
+
maxDescription: 5000,
|
|
30
|
+
maxName: 200,
|
|
31
|
+
maxOrganization: 200,
|
|
32
|
+
maxEmail: 254,
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Where a claim is in its review.
|
|
36
|
+
*
|
|
37
|
+
* `received → reviewing → actioned | rejected`; an actioned claim can be
|
|
38
|
+
* answered by the content's owner (`counter_noticed`), which ends either in
|
|
39
|
+
* `reinstated` (the content is served again) or `upheld` (it stays blocked).
|
|
40
|
+
* A claim can also be rejected straight from `received`.
|
|
41
|
+
*/
|
|
42
|
+
export const CONTENT_CLAIM_STATES = [
|
|
43
|
+
'received',
|
|
44
|
+
'reviewing',
|
|
45
|
+
'actioned',
|
|
46
|
+
'rejected',
|
|
47
|
+
'counter_noticed',
|
|
48
|
+
'reinstated',
|
|
49
|
+
'upheld',
|
|
50
|
+
];
|
|
51
|
+
/** The states in which a claim is still open: its evidence is kept and its blocks can change. */
|
|
52
|
+
export const OPEN_CONTENT_CLAIM_STATES = [
|
|
53
|
+
'received',
|
|
54
|
+
'reviewing',
|
|
55
|
+
'actioned',
|
|
56
|
+
'counter_noticed',
|
|
57
|
+
];
|
|
58
|
+
/** The states a claim ends in. */
|
|
59
|
+
export const CLOSED_CONTENT_CLAIM_STATES = [
|
|
60
|
+
'rejected',
|
|
61
|
+
'reinstated',
|
|
62
|
+
'upheld',
|
|
63
|
+
];
|
|
64
|
+
/**
|
|
65
|
+
* The kinds whose claimant asserts a right in a work or a mark. Only these
|
|
66
|
+
* carry the link to the original — a privacy or illegal-content report
|
|
67
|
+
* asserts no ownership.
|
|
68
|
+
*/
|
|
69
|
+
export function isRightsClaim(kind) {
|
|
70
|
+
return kind === 'copyright' || kind === 'trademark';
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A request to erase the claimant's OWN personal data (GDPR art. 17): a
|
|
74
|
+
* privacy claim made by the person it is about. Oxy must answer it within one
|
|
75
|
+
* month (art. 12(3)); the claim carries that deadline.
|
|
76
|
+
*/
|
|
77
|
+
export function isErasureRequest(kind, relationship) {
|
|
78
|
+
return kind === 'privacy' && relationship === 'affected_person';
|
|
79
|
+
}
|
|
80
|
+
/** Days Oxy has to answer an erasure request ({@link isErasureRequest}). */
|
|
81
|
+
export const ERASURE_REQUEST_DEADLINE_DAYS = 30;
|
|
82
|
+
/**
|
|
83
|
+
* Stable error codes a takedown produces elsewhere in the API. A file whose
|
|
84
|
+
* bytes are on the blocklist cannot be uploaded, imported or copied by anyone
|
|
85
|
+
* (HTTP 451).
|
|
86
|
+
*/
|
|
87
|
+
export const CONTENT_CLAIM_ERROR_CODES = {
|
|
88
|
+
/** The submission failed validation; `fields` names each problem. */
|
|
89
|
+
invalid: 'CONTENT_CLAIM_INVALID',
|
|
90
|
+
/** These exact bytes were taken down after a claim and cannot be stored again. */
|
|
91
|
+
contentBlocked: 'CONTENT_BLOCKED',
|
|
92
|
+
/** A counter-notice failed validation; `fields` names each problem. */
|
|
93
|
+
counterNoticeInvalid: 'CONTENT_CLAIM_COUNTER_NOTICE_INVALID',
|
|
94
|
+
/** The files named are blocked by more than one claim: name the claim's reference. */
|
|
95
|
+
counterNoticeAmbiguous: 'CONTENT_CLAIM_COUNTER_NOTICE_AMBIGUOUS',
|
|
96
|
+
/** This account already sent a counter-notice for this claim. */
|
|
97
|
+
counterNoticeExists: 'CONTENT_CLAIM_COUNTER_NOTICE_EXISTS',
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* A claim's reference as the claimant and the owner are sent it:
|
|
101
|
+
* `OXY-XXXX-XXXX`, Crockford base32 (no I, L, O or U).
|
|
102
|
+
*/
|
|
103
|
+
export const CONTENT_CLAIM_REFERENCE_PATTERN = /^OXY-[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}$/;
|
|
104
|
+
/** The bounds of an owner's counter-notice. The website fills its validation messages from these. */
|
|
105
|
+
export const CONTENT_CLAIM_COUNTER_NOTICE_LIMITS = {
|
|
106
|
+
maxFileIds: 50,
|
|
107
|
+
maxFileId: 64,
|
|
108
|
+
maxName: 200,
|
|
109
|
+
maxAddress: 500,
|
|
110
|
+
minPhone: 5,
|
|
111
|
+
maxPhone: 40,
|
|
112
|
+
maxEmail: 254,
|
|
113
|
+
minMaterial: 10,
|
|
114
|
+
maxMaterial: 2000,
|
|
115
|
+
maxLocation: 2048,
|
|
116
|
+
minExplanation: 20,
|
|
117
|
+
maxExplanation: 5000,
|
|
118
|
+
maxSignature: 200,
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* The statements an owner's counter-notice makes, word for word — what the
|
|
122
|
+
* form shows beside each box (17 U.S.C. § 512(g)(3)(C) and (D), and what Oxy
|
|
123
|
+
* does with the notice). The API records {@link COUNTER_NOTICE_STATEMENT_VERSION}
|
|
124
|
+
* with every notice, so the wording a person agreed to can always be told
|
|
125
|
+
* apart from a later one. Change the text only together with the version.
|
|
126
|
+
*/
|
|
127
|
+
export const COUNTER_NOTICE_STATEMENTS = {
|
|
128
|
+
perjury: 'I swear, under penalty of perjury, that I have a good faith belief that the material identified above was removed or disabled as a result of a mistake or misidentification of the material to be removed or disabled.',
|
|
129
|
+
jurisdiction: 'I consent to the jurisdiction of the Federal District Court for the judicial district in which my address is located or, if my address is outside of the United States, any judicial district in which Oxy may be found.',
|
|
130
|
+
serviceOfProcess: 'I will accept service of process from the person who provided the notification of the claimed infringement, or an agent of such person.',
|
|
131
|
+
disclosure: 'I understand that Oxy sends a copy of this counter-notice, including my name and contact details, to the person who made the claim, as the law requires.',
|
|
132
|
+
};
|
|
133
|
+
/** The version of {@link COUNTER_NOTICE_STATEMENTS} in force. */
|
|
134
|
+
export const COUNTER_NOTICE_STATEMENT_VERSION = '2026-10-16';
|
package/dist/esm/client/index.js
CHANGED
|
@@ -27,7 +27,7 @@ export { SESSION_ACCOUNTS_CHANGED_EVENT, activeTokenCheck, deviceBackgroundCrede
|
|
|
27
27
|
export { IDENTITY_LINK_QR_PREFIX, IDENTITY_LINK_STATUSES, buildIdentityLinkQrPayload, identityLinkCreateResponseCheck, identityLinkStateCheck, parseIdentityLinkQrPayload, } from './identityLink.js';
|
|
28
28
|
export { IDENTITY_ERROR_CODES, IDENTITY_PROOF_ACTIONS, IDENTITY_PROOF_ACTION_VALUES, IDENTITY_PROOF_AUDIENCE, IDENTITY_PROOF_CHALLENGE_TTL_MS, IDENTITY_PROOF_DOMAIN, IDENTITY_PROOF_VERSION, buildIdentityProofMessage, canonicalJson, } from './identityProof.js';
|
|
29
29
|
export { ORGANIZATION_ALIAS_KINDS, ORGANIZATION_CLAIM_DECISIONS, ORGANIZATION_CLAIM_STATUSES, ORGANIZATION_ERROR_CODES, ORGANIZATION_NAME_MAX_LENGTH, ORGANIZATION_STATUSES, ORGANIZATION_TYPES, createOrganizationCheck, organizationBatchCheck, organizationCheck, organizationClaimCheck, organizationClaimDetailCheck, organizationClaimInputCheck, organizationClaimListCheck, organizationClaimReviewCheck, organizationCurationResultCheck, organizationExternalCheck, organizationExternalResolutionCheck, organizationImportBatchResultCheck, organizationListCheck, organizationNameCheck, updateOrganizationCheck, } from './organizations.js';
|
|
30
|
-
export { cancelProductSubscriptionCheck, personalPlanCatalogueCheck, personalPlanCheckoutRequestCheck, personalPlanCheckoutResultCheck, productSubscriptionCancellationResultCheck, productSubscriptionsResponseCheck, subjectProductAccessCheck, subjectProductAccessQueryCheck, subscriptionCreditGrantsResponseCheck, } from './products.js';
|
|
30
|
+
export { cancelProductSubscriptionCheck, personalPlanCatalogueCheck, personalPlanCheckoutRequestCheck, personalPlanCheckoutResultCheck, productPlanCatalogueCheck, productPlanCheckoutRequestCheck, productSubscriptionCancellationResultCheck, productSubscriptionRenewalRequestCheck, productSubscriptionsResponseCheck, subjectProductAccessCheck, subjectProductAccessQueryCheck, subscriptionCreditGrantsResponseCheck, } from './products.js';
|
|
31
31
|
export { recommendationRequestCheck } from './recommendations.js';
|
|
32
32
|
export { isFullReputationBalance } from './reputation.js';
|
|
33
33
|
export { PURCHASE_APPROVAL_STATUSES, SAFETY_POLICY_VERSION, SUPERVISED_ACTIONS, SUPERVISION_CONSENT_VERSION, SUPERVISION_INVITATION_STATUSES, SUPERVISION_STATUSES, profileAccessDecisionCheck, profileAccessInputCheck, purchaseApprovalCheck, purchaseApprovalListCheck, purchaseReviewInputCheck, safetyOrganizationsInputCheck, safetyOrganizationsResponseCheck, safetyPeopleInputCheck, safetyPeopleResponseCheck, supervisionAcceptInputCheck, supervisionContactInputCheck, supervisionContactsResponseCheck, supervisionInvitationCheck, supervisionInviteInputCheck, supervisionReviewInputCheck, supervisionStateCheck, } from './safety.js';
|
|
@@ -35,3 +35,4 @@ export { publicApplicationCheck } from './sessionStatus.js';
|
|
|
35
35
|
export { EMAIL_ACCOUNTS_MAX, EMAIL_SIGNIN_CHOICE_TTL_MS, EMAIL_SIGNIN_LINK_TTL_MS, EMAIL_SIGNIN_LONG_CODE_ALPHABET, EMAIL_SIGNIN_LONG_CODE_LENGTH, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, REAUTH_ACTIONS, SIGNIN_SECOND_FACTOR_MAX_ATTEMPTS, SIGNIN_SECOND_FACTOR_TTL_MS, SIGN_IN_ERROR_CODES, TOTP_BACKUP_CODE_COUNT, TOTP_DIGITS, TOTP_PERIOD_SECONDS, emailSignInChooseAccountCheck, emailSignInLinkResponseCheck, emailSignInPendingCheck, emailSignInStartResponseCheck, isEmailSignInChooseAccount, isSecondFactorRequired, normalizeEmailSignInCode, reauthProofCheck, secondFactorRequiredCheck, signInMethodsCheck, totpBackupCodesResponseCheck, totpEnrollResponseCheck, } from './signIn.js';
|
|
36
36
|
export { BOT_USERNAME_INVALID_MESSAGE, BOT_USERNAME_SUFFIX, NUMERIC_USERNAME_MESSAGE, RESERVED_USERNAMES, RESERVED_USERNAME_MESSAGE, USERNAME_INVALID_MESSAGE, USERNAME_MAX_LENGTH, USERNAME_MIN_LENGTH, USERNAME_PATTERN_SOURCE, applyBotUsernameSuffix, botUsernameCheck, stripDisallowedUsernameCharacters, usernameCheck, usernameCheckForAccountKind, } from './username.js';
|
|
37
37
|
export { userNameCheck } from './userResponse.js';
|
|
38
|
+
export { CLAIMANT_RELATIONSHIPS, CLOSED_CONTENT_CLAIM_STATES, CONTENT_CLAIM_COUNTER_NOTICE_LIMITS, CONTENT_CLAIM_ERROR_CODES, CONTENT_CLAIM_KINDS, CONTENT_CLAIM_LIMITS, CONTENT_CLAIM_REFERENCE_PATTERN, CONTENT_CLAIM_STATES, COUNTER_NOTICE_STATEMENT_VERSION, COUNTER_NOTICE_STATEMENTS, ERASURE_REQUEST_DEADLINE_DAYS, OPEN_CONTENT_CLAIM_STATES, isErasureRequest, isRightsClaim, } from './contentClaims.js';
|
|
@@ -13,6 +13,15 @@ const oxyAccountId = branded(string(minLength(1), maxLength(64)));
|
|
|
13
13
|
const actionKey = string(minLength(8), maxLength(160), pattern(/^[a-zA-Z0-9_-]+$/));
|
|
14
14
|
const combination = oneOf(['maximum', 'sum', 'exclusive']);
|
|
15
15
|
const origin = oneOf(['individual', 'bundle']);
|
|
16
|
+
const purchase = oneOf(['unavailable', 'available']);
|
|
17
|
+
const displayPrice = (interval) => strictObject({
|
|
18
|
+
currency: string(pattern(/^[A-Z]{3}$/)),
|
|
19
|
+
amountMinorUnits: version,
|
|
20
|
+
interval,
|
|
21
|
+
trial: literal('none'),
|
|
22
|
+
taxTreatment: literal('inclusive'),
|
|
23
|
+
merchantTotal: literal('final'),
|
|
24
|
+
});
|
|
16
25
|
const period = (message) => refine(strictObject({ start: string(datetime), end: string(datetime) }), (value) => Date.parse(value.end) > Date.parse(value.start), message);
|
|
17
26
|
const productBenefit = discriminatedUnion('kind', {
|
|
18
27
|
capability: strictObject({ kind: literal('capability'), productId: identifier, key: identifier }),
|
|
@@ -57,29 +66,41 @@ export const subjectProductAccessCheck = exact()(strictObject({
|
|
|
57
66
|
export const personalPlanCatalogueCheck = exact()(refine(strictObject({
|
|
58
67
|
schemaVersion: literal(1),
|
|
59
68
|
state: oneOf(['unconfigured', 'configured']),
|
|
60
|
-
purchase
|
|
69
|
+
purchase,
|
|
61
70
|
plans: array(strictObject({
|
|
62
71
|
offerId: identifier,
|
|
63
72
|
offerVersion: version,
|
|
64
73
|
displayName,
|
|
65
74
|
audience: literal('personal'),
|
|
66
75
|
kind: literal('oxy_one'),
|
|
67
|
-
price: optional(
|
|
68
|
-
currency: string(pattern(/^[A-Z]{3}$/)),
|
|
69
|
-
amountMinorUnits: version,
|
|
70
|
-
interval: literal('month'),
|
|
71
|
-
trial: literal('none'),
|
|
72
|
-
taxTreatment: literal('inclusive'),
|
|
73
|
-
merchantTotal: literal('final'),
|
|
74
|
-
})),
|
|
76
|
+
price: optional(displayPrice(literal('month'))),
|
|
75
77
|
benefits: array(strictObject({ displayName, benefit: productBenefit })),
|
|
76
78
|
})),
|
|
77
79
|
}), (value) => (value.state === 'unconfigured') === (value.plans.length === 0), 'Catalogue state must match published plans'));
|
|
78
|
-
export const
|
|
80
|
+
export const productPlanCatalogueCheck = exact()(strictObject({
|
|
81
|
+
schemaVersion: literal(1),
|
|
82
|
+
productId: identifier,
|
|
83
|
+
purchase,
|
|
84
|
+
plans: array(strictObject({
|
|
85
|
+
productId: identifier,
|
|
86
|
+
offerId: identifier,
|
|
87
|
+
offerVersion: version,
|
|
88
|
+
displayName,
|
|
89
|
+
price: displayPrice(oneOf(['month', 'year'])),
|
|
90
|
+
})),
|
|
91
|
+
}));
|
|
92
|
+
const checkoutRequestShape = {
|
|
79
93
|
expectedSubjectAccountId: oxyAccountId,
|
|
80
94
|
offerId: identifier,
|
|
81
95
|
offerVersion: version,
|
|
82
96
|
idempotencyKey: actionKey,
|
|
97
|
+
};
|
|
98
|
+
export const personalPlanCheckoutRequestCheck = exact()(strictObject(checkoutRequestShape));
|
|
99
|
+
export const productPlanCheckoutRequestCheck = exact()(strictObject({ ...checkoutRequestShape, productId: identifier }));
|
|
100
|
+
export const productSubscriptionRenewalRequestCheck = exact()(strictObject({
|
|
101
|
+
expectedSubjectAccountId: oxyAccountId,
|
|
102
|
+
sourceId: identifier,
|
|
103
|
+
idempotencyKey: actionKey,
|
|
83
104
|
}));
|
|
84
105
|
export const personalPlanCheckoutResultCheck = exact()(discriminatedUnion('state', {
|
|
85
106
|
unconfigured: strictObject({
|
|
@@ -111,6 +132,7 @@ export const productSubscriptionsResponseCheck = exact()(strictObject({
|
|
|
111
132
|
period: billingPeriod,
|
|
112
133
|
cancelAtPeriodEnd: boolean,
|
|
113
134
|
canCancel: boolean,
|
|
135
|
+
canRenew: optional(boolean),
|
|
114
136
|
offers: array(strictObject({
|
|
115
137
|
segmentId: identifier,
|
|
116
138
|
offerId: identifier,
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content claims (ADR 0043 D10): the public "Report content / rights claim"
|
|
3
|
+
* intake, `POST /content-claims`, and the staff review of a claim.
|
|
4
|
+
*
|
|
5
|
+
* {@link contentClaimSubmissionSchema} is the request body the API parses and
|
|
6
|
+
* the Oxy website validates its form with before posting, so the form cannot
|
|
7
|
+
* accept what the API would answer 400 to. Every validation message is a
|
|
8
|
+
* stable KEY under `contentClaim.errors.*` (filled with
|
|
9
|
+
* {@link CONTENT_CLAIM_LIMITS} by whoever shows it), never English: the website
|
|
10
|
+
* translates the key, and the API answers its own English sentence for it in
|
|
11
|
+
* `fields`.
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
// The Zod-free half of this contract lives in `./client/` (`@oxy.so/contracts/client`);
|
|
15
|
+
// it is re-exported here so the root entry carries the whole contract.
|
|
16
|
+
import { CLAIMANT_RELATIONSHIPS, CONTENT_CLAIM_COUNTER_NOTICE_LIMITS, CONTENT_CLAIM_KINDS, CONTENT_CLAIM_LIMITS, CONTENT_CLAIM_REFERENCE_PATTERN, } from './client/contentClaims.js';
|
|
17
|
+
export { CLAIMANT_RELATIONSHIPS, CLOSED_CONTENT_CLAIM_STATES, CONTENT_CLAIM_COUNTER_NOTICE_LIMITS, CONTENT_CLAIM_ERROR_CODES, CONTENT_CLAIM_KINDS, CONTENT_CLAIM_LIMITS, CONTENT_CLAIM_REFERENCE_PATTERN, CONTENT_CLAIM_STATES, COUNTER_NOTICE_STATEMENT_VERSION, COUNTER_NOTICE_STATEMENTS, ERASURE_REQUEST_DEADLINE_DAYS, OPEN_CONTENT_CLAIM_STATES, isErasureRequest, isRightsClaim, } from './client/contentClaims.js';
|
|
18
|
+
/** Every key a submission's validation can produce, under `contentClaim.errors.*`. */
|
|
19
|
+
export const CONTENT_CLAIM_VALIDATION_KEYS = [
|
|
20
|
+
'contentClaim.errors.kind',
|
|
21
|
+
'contentClaim.errors.contentUrlsRequired',
|
|
22
|
+
'contentClaim.errors.contentUrlsTooMany',
|
|
23
|
+
'contentClaim.errors.contentUrlInvalid',
|
|
24
|
+
'contentClaim.errors.descriptionShort',
|
|
25
|
+
'contentClaim.errors.descriptionLong',
|
|
26
|
+
'contentClaim.errors.originalWorkUrl',
|
|
27
|
+
'contentClaim.errors.name',
|
|
28
|
+
'contentClaim.errors.email',
|
|
29
|
+
'contentClaim.errors.organization',
|
|
30
|
+
'contentClaim.errors.relationship',
|
|
31
|
+
'contentClaim.errors.goodFaith',
|
|
32
|
+
'contentClaim.errors.accurate',
|
|
33
|
+
'contentClaim.errors.signature',
|
|
34
|
+
];
|
|
35
|
+
function isHttpsUrl(value) {
|
|
36
|
+
try {
|
|
37
|
+
const url = new URL(value);
|
|
38
|
+
return url.protocol === 'https:' && url.hostname.length > 0;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const httpsUrl = (message) => z.string().trim().max(CONTENT_CLAIM_LIMITS.maxUrlLength, message).refine(isHttpsUrl, message);
|
|
45
|
+
/**
|
|
46
|
+
* `POST /content-claims` — a claim, as the claimant sends it. Strict: an
|
|
47
|
+
* unknown key is refused rather than dropped.
|
|
48
|
+
*/
|
|
49
|
+
export const contentClaimSubmissionSchema = z
|
|
50
|
+
.object({
|
|
51
|
+
kind: z.enum(CONTENT_CLAIM_KINDS, { message: 'contentClaim.errors.kind' }),
|
|
52
|
+
contentUrls: z
|
|
53
|
+
.array(httpsUrl('contentClaim.errors.contentUrlInvalid'), {
|
|
54
|
+
message: 'contentClaim.errors.contentUrlsRequired',
|
|
55
|
+
})
|
|
56
|
+
.min(CONTENT_CLAIM_LIMITS.minUrls, 'contentClaim.errors.contentUrlsRequired')
|
|
57
|
+
.max(CONTENT_CLAIM_LIMITS.maxUrls, 'contentClaim.errors.contentUrlsTooMany'),
|
|
58
|
+
description: z
|
|
59
|
+
.string({ message: 'contentClaim.errors.descriptionShort' })
|
|
60
|
+
.trim()
|
|
61
|
+
.min(CONTENT_CLAIM_LIMITS.minDescription, 'contentClaim.errors.descriptionShort')
|
|
62
|
+
.max(CONTENT_CLAIM_LIMITS.maxDescription, 'contentClaim.errors.descriptionLong'),
|
|
63
|
+
originalWorkUrl: httpsUrl('contentClaim.errors.originalWorkUrl').optional(),
|
|
64
|
+
claimant: z
|
|
65
|
+
.object({
|
|
66
|
+
name: z
|
|
67
|
+
.string({ message: 'contentClaim.errors.name' })
|
|
68
|
+
.trim()
|
|
69
|
+
.min(1, 'contentClaim.errors.name')
|
|
70
|
+
.max(CONTENT_CLAIM_LIMITS.maxName, 'contentClaim.errors.name'),
|
|
71
|
+
email: z
|
|
72
|
+
.string({ message: 'contentClaim.errors.email' })
|
|
73
|
+
.trim()
|
|
74
|
+
.email('contentClaim.errors.email')
|
|
75
|
+
.max(CONTENT_CLAIM_LIMITS.maxEmail, 'contentClaim.errors.email'),
|
|
76
|
+
organization: z
|
|
77
|
+
.string({ message: 'contentClaim.errors.organization' })
|
|
78
|
+
.trim()
|
|
79
|
+
.max(CONTENT_CLAIM_LIMITS.maxOrganization, 'contentClaim.errors.organization')
|
|
80
|
+
.optional(),
|
|
81
|
+
relationship: z.enum(CLAIMANT_RELATIONSHIPS, {
|
|
82
|
+
message: 'contentClaim.errors.relationship',
|
|
83
|
+
}),
|
|
84
|
+
}, { message: 'contentClaim.errors.name' })
|
|
85
|
+
.strict(),
|
|
86
|
+
statements: z
|
|
87
|
+
.object({
|
|
88
|
+
goodFaith: z.literal(true, {
|
|
89
|
+
errorMap: () => ({ message: 'contentClaim.errors.goodFaith' }),
|
|
90
|
+
}),
|
|
91
|
+
accurate: z.literal(true, {
|
|
92
|
+
errorMap: () => ({ message: 'contentClaim.errors.accurate' }),
|
|
93
|
+
}),
|
|
94
|
+
}, { message: 'contentClaim.errors.goodFaith' })
|
|
95
|
+
.strict(),
|
|
96
|
+
signature: z
|
|
97
|
+
.string({ message: 'contentClaim.errors.signature' })
|
|
98
|
+
.trim()
|
|
99
|
+
.min(1, 'contentClaim.errors.signature')
|
|
100
|
+
.max(CONTENT_CLAIM_LIMITS.maxName, 'contentClaim.errors.signature'),
|
|
101
|
+
})
|
|
102
|
+
.strict();
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// The staff review (`/content-claims` behind the `content:claims` capability)
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
/** How a target's URL was understood. */
|
|
107
|
+
export const CONTENT_CLAIM_TARGET_RESOLUTIONS = [
|
|
108
|
+
/** An Oxy file: `cloud.oxy.so/<id>`, `/cdn/<id>`, `/assets/<id>…`, a stored key. */
|
|
109
|
+
'file',
|
|
110
|
+
/** A page of an Oxy app or anything else: a person attaches the files it shows. */
|
|
111
|
+
'unresolved',
|
|
112
|
+
];
|
|
113
|
+
/** Who added a target: the claimant's URL, or staff attaching a file by id. */
|
|
114
|
+
export const CONTENT_CLAIM_TARGET_SOURCES = ['claimant', 'staff'];
|
|
115
|
+
/** What the audit trail records. */
|
|
116
|
+
export const CONTENT_CLAIM_EVENT_ACTIONS = [
|
|
117
|
+
'submitted',
|
|
118
|
+
'review_started',
|
|
119
|
+
'targets_attached',
|
|
120
|
+
'targets_blocked',
|
|
121
|
+
'rejected',
|
|
122
|
+
'counter_notice_recorded',
|
|
123
|
+
/** The owner of the blocked content sent a counter-notice themselves (`POST /content-claims/counter-notices`). */
|
|
124
|
+
'counter_notice_submitted',
|
|
125
|
+
'reinstated',
|
|
126
|
+
'upheld',
|
|
127
|
+
'note_added',
|
|
128
|
+
];
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// The owner's counter-notice (`POST /content-claims/counter-notices`)
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
/** Every key a counter-notice's validation can produce, under `contentClaim.counterNotice.errors.*`. */
|
|
133
|
+
export const CONTENT_CLAIM_COUNTER_NOTICE_VALIDATION_KEYS = [
|
|
134
|
+
'contentClaim.counterNotice.errors.claim',
|
|
135
|
+
'contentClaim.counterNotice.errors.reference',
|
|
136
|
+
'contentClaim.counterNotice.errors.claimId',
|
|
137
|
+
'contentClaim.counterNotice.errors.fileIds',
|
|
138
|
+
'contentClaim.counterNotice.errors.name',
|
|
139
|
+
'contentClaim.counterNotice.errors.address',
|
|
140
|
+
'contentClaim.counterNotice.errors.phone',
|
|
141
|
+
'contentClaim.counterNotice.errors.email',
|
|
142
|
+
'contentClaim.counterNotice.errors.materialDescription',
|
|
143
|
+
'contentClaim.counterNotice.errors.materialLocation',
|
|
144
|
+
'contentClaim.counterNotice.errors.explanation',
|
|
145
|
+
'contentClaim.counterNotice.errors.perjury',
|
|
146
|
+
'contentClaim.counterNotice.errors.jurisdiction',
|
|
147
|
+
'contentClaim.counterNotice.errors.serviceOfProcess',
|
|
148
|
+
'contentClaim.counterNotice.errors.disclosure',
|
|
149
|
+
'contentClaim.counterNotice.errors.signature',
|
|
150
|
+
];
|
|
151
|
+
const N = CONTENT_CLAIM_COUNTER_NOTICE_LIMITS;
|
|
152
|
+
const boundedText = (min, max, message) => z.string({ message }).trim().min(min, message).max(max, message);
|
|
153
|
+
const affirmed = (message) => z.literal(true, { errorMap: () => ({ message }) });
|
|
154
|
+
/**
|
|
155
|
+
* `POST /content-claims/counter-notices` — the owner of content a claim took
|
|
156
|
+
* down disputes it (17 U.S.C. § 512(g)(3)). Signed in: the API answers only
|
|
157
|
+
* for files the caller owns.
|
|
158
|
+
*
|
|
159
|
+
* Names the claim by its `reference` (the `OXY-XXXX-XXXX` the owner was
|
|
160
|
+
* emailed), its `claimId`, or the blocked `fileIds` — at least one. With
|
|
161
|
+
* `fileIds` the notice covers exactly those files; without, every one of the
|
|
162
|
+
* caller's files the claim blocked. Strict: an unknown key is refused.
|
|
163
|
+
*/
|
|
164
|
+
export const contentClaimCounterNoticeSchema = z
|
|
165
|
+
.object({
|
|
166
|
+
reference: z
|
|
167
|
+
.string({ message: 'contentClaim.counterNotice.errors.reference' })
|
|
168
|
+
.trim()
|
|
169
|
+
.toUpperCase()
|
|
170
|
+
.regex(CONTENT_CLAIM_REFERENCE_PATTERN, 'contentClaim.counterNotice.errors.reference')
|
|
171
|
+
.optional(),
|
|
172
|
+
claimId: z
|
|
173
|
+
.string({ message: 'contentClaim.counterNotice.errors.claimId' })
|
|
174
|
+
.trim()
|
|
175
|
+
.uuid('contentClaim.counterNotice.errors.claimId')
|
|
176
|
+
.optional(),
|
|
177
|
+
fileIds: z
|
|
178
|
+
.array(boundedText(1, N.maxFileId, 'contentClaim.counterNotice.errors.fileIds'), {
|
|
179
|
+
message: 'contentClaim.counterNotice.errors.fileIds',
|
|
180
|
+
})
|
|
181
|
+
.min(1, 'contentClaim.counterNotice.errors.fileIds')
|
|
182
|
+
.max(N.maxFileIds, 'contentClaim.counterNotice.errors.fileIds')
|
|
183
|
+
.optional(),
|
|
184
|
+
subscriber: z
|
|
185
|
+
.object({
|
|
186
|
+
name: boundedText(1, N.maxName, 'contentClaim.counterNotice.errors.name'),
|
|
187
|
+
address: boundedText(1, N.maxAddress, 'contentClaim.counterNotice.errors.address'),
|
|
188
|
+
phone: boundedText(N.minPhone, N.maxPhone, 'contentClaim.counterNotice.errors.phone').regex(/^\+?[0-9 ().-]+$/, 'contentClaim.counterNotice.errors.phone'),
|
|
189
|
+
email: z
|
|
190
|
+
.string({ message: 'contentClaim.counterNotice.errors.email' })
|
|
191
|
+
.trim()
|
|
192
|
+
.email('contentClaim.counterNotice.errors.email')
|
|
193
|
+
.max(N.maxEmail, 'contentClaim.counterNotice.errors.email'),
|
|
194
|
+
}, { message: 'contentClaim.counterNotice.errors.name' })
|
|
195
|
+
.strict(),
|
|
196
|
+
material: z
|
|
197
|
+
.object({
|
|
198
|
+
/** What was removed. */
|
|
199
|
+
description: boundedText(N.minMaterial, N.maxMaterial, 'contentClaim.counterNotice.errors.materialDescription'),
|
|
200
|
+
/** Where it appeared before it was removed (a URL, or where in an app). */
|
|
201
|
+
location: boundedText(1, N.maxLocation, 'contentClaim.counterNotice.errors.materialLocation'),
|
|
202
|
+
}, { message: 'contentClaim.counterNotice.errors.materialDescription' })
|
|
203
|
+
.strict(),
|
|
204
|
+
/** Why it was a mistake or a misidentification, in the owner's words. */
|
|
205
|
+
explanation: boundedText(N.minExplanation, N.maxExplanation, 'contentClaim.counterNotice.errors.explanation'),
|
|
206
|
+
/** Each of `COUNTER_NOTICE_STATEMENTS`, affirmed. */
|
|
207
|
+
statements: z
|
|
208
|
+
.object({
|
|
209
|
+
perjury: affirmed('contentClaim.counterNotice.errors.perjury'),
|
|
210
|
+
jurisdiction: affirmed('contentClaim.counterNotice.errors.jurisdiction'),
|
|
211
|
+
serviceOfProcess: affirmed('contentClaim.counterNotice.errors.serviceOfProcess'),
|
|
212
|
+
disclosure: affirmed('contentClaim.counterNotice.errors.disclosure'),
|
|
213
|
+
}, { message: 'contentClaim.counterNotice.errors.perjury' })
|
|
214
|
+
.strict(),
|
|
215
|
+
/** The owner's full legal name, typed as their signature. */
|
|
216
|
+
signature: boundedText(1, N.maxSignature, 'contentClaim.counterNotice.errors.signature'),
|
|
217
|
+
})
|
|
218
|
+
.strict()
|
|
219
|
+
.superRefine((value, ctx) => {
|
|
220
|
+
if (value.reference === undefined &&
|
|
221
|
+
value.claimId === undefined &&
|
|
222
|
+
value.fileIds === undefined) {
|
|
223
|
+
ctx.addIssue({
|
|
224
|
+
code: z.ZodIssueCode.custom,
|
|
225
|
+
path: ['reference'],
|
|
226
|
+
message: 'contentClaim.counterNotice.errors.claim',
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
});
|
package/dist/esm/index.js
CHANGED
|
@@ -201,9 +201,10 @@ export { meteredGenerationSchema } from './inference/economics.js';
|
|
|
201
201
|
export { canonicalCapabilityJson, inputSatisfiesCapabilityLimits, isLoopbackOrigin, } from './capabilityBindings.js';
|
|
202
202
|
export * from './agentKeys.js';
|
|
203
203
|
export { productSubscriptionSummarySchema, productSubscriptionsResponseSchema, subscriptionCreditGrantSchema, subscriptionCreditGrantsResponseSchema, cancelProductSubscriptionSchema, productSubscriptionCancellationResultSchema, } from './products/billing.js';
|
|
204
|
-
export { personalPlanDisplayPriceSchema, personalPlanCatalogueSchema, } from './products/plans.js';
|
|
204
|
+
export { personalPlanDisplayPriceSchema, personalPlanCatalogueSchema, productPlanCatalogueSchema, } from './products/plans.js';
|
|
205
205
|
export * from './products/checkout.js';
|
|
206
206
|
export * from './organizations.js';
|
|
207
207
|
export * from './safety.js';
|
|
208
208
|
export { serviceTokenScopesSchema, serviceTokenOptionsSchema, } from './serviceToken.js';
|
|
209
209
|
export * from './applicationRoles.js';
|
|
210
|
+
export * from './contentClaims.js';
|
|
@@ -30,3 +30,16 @@ export const personalPlanCheckoutResultSchema = z.discriminatedUnion('state', [
|
|
|
30
30
|
z.object({ state: z.literal('closed'), intentId: z.string().min(1).max(160) }).strict(),
|
|
31
31
|
z.object({ state: z.literal('fulfilled'), intentId: z.string().min(1).max(160) }).strict(),
|
|
32
32
|
]);
|
|
33
|
+
/** Explicit individual product offer; still owned and paid through Oxy. */
|
|
34
|
+
export const productPlanCheckoutRequestSchema = personalPlanCheckoutRequestSchema
|
|
35
|
+
.extend({
|
|
36
|
+
productId: z.string().min(1).max(160),
|
|
37
|
+
})
|
|
38
|
+
.strict();
|
|
39
|
+
export const productSubscriptionRenewalRequestSchema = z
|
|
40
|
+
.object({
|
|
41
|
+
expectedSubjectAccountId: oxyAccountIdSchema,
|
|
42
|
+
sourceId: z.string().min(1).max(160),
|
|
43
|
+
idempotencyKey: personalPlanCheckoutRequestSchema.shape.idempotencyKey,
|
|
44
|
+
})
|
|
45
|
+
.strict();
|
|
@@ -16,7 +16,7 @@ export const personalPlanCatalogueSchema = z
|
|
|
16
16
|
.object({
|
|
17
17
|
schemaVersion: z.literal(1),
|
|
18
18
|
state: z.enum(['unconfigured', 'configured']),
|
|
19
|
-
purchase: z.
|
|
19
|
+
purchase: z.enum(['unavailable', 'available']),
|
|
20
20
|
plans: z.array(z
|
|
21
21
|
.object({
|
|
22
22
|
offerId: z.string().min(1).max(160),
|
|
@@ -33,3 +33,19 @@ export const personalPlanCatalogueSchema = z
|
|
|
33
33
|
})
|
|
34
34
|
.strict()
|
|
35
35
|
.refine((value) => (value.state === 'unconfigured') === (value.plans.length === 0), 'Catalogue state must match published plans');
|
|
36
|
+
export const productPlanCatalogueSchema = z
|
|
37
|
+
.object({
|
|
38
|
+
schemaVersion: z.literal(1),
|
|
39
|
+
productId: z.string().min(1).max(160),
|
|
40
|
+
purchase: z.enum(['unavailable', 'available']),
|
|
41
|
+
plans: z.array(z
|
|
42
|
+
.object({
|
|
43
|
+
productId: z.string().min(1).max(160),
|
|
44
|
+
offerId: z.string().min(1).max(160),
|
|
45
|
+
offerVersion: z.number().int().positive().safe(),
|
|
46
|
+
displayName: z.string().min(1).max(100),
|
|
47
|
+
price: personalPlanDisplayPriceSchema.extend({ interval: z.enum(['month', 'year']) }),
|
|
48
|
+
})
|
|
49
|
+
.strict()),
|
|
50
|
+
})
|
|
51
|
+
.strict();
|
package/dist/esm/userResponse.js
CHANGED
|
@@ -241,6 +241,10 @@ export const userResponseSchema = z
|
|
|
241
241
|
* so it is present on the current-user DTO (`GET /users/me`,
|
|
242
242
|
* `GET /session/user/:sessionId`) and absent until the user sets it.
|
|
243
243
|
*/
|
|
244
|
+
userPreferences: z
|
|
245
|
+
.object({ displayCurrency: z.enum(['USD', 'EUR', 'FAIR']).optional() })
|
|
246
|
+
.catchall(z.unknown())
|
|
247
|
+
.optional(),
|
|
244
248
|
themePreference: themePreferenceSchema.optional(),
|
|
245
249
|
personalization: z
|
|
246
250
|
.object({
|
|
@@ -301,7 +305,10 @@ export const userProfileUpdateSchema = z
|
|
|
301
305
|
languages: z.array(z.string()).optional(),
|
|
302
306
|
accountExpiresAfterInactivityDays: z.number().nullable().optional(),
|
|
303
307
|
notificationPreferences: z.record(z.unknown()).optional(),
|
|
304
|
-
userPreferences: z
|
|
308
|
+
userPreferences: z
|
|
309
|
+
.object({ displayCurrency: z.enum(['USD', 'EUR', 'FAIR']).optional() })
|
|
310
|
+
.catchall(z.unknown())
|
|
311
|
+
.optional(),
|
|
305
312
|
privacySettings: z.record(z.unknown()).optional(),
|
|
306
313
|
/**
|
|
307
314
|
* Portable theme preference. Written through the same `PUT /users/me`
|