@oxy.so/contracts 4.17.0 → 4.22.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/accountAccess.js +72 -0
- package/dist/cjs/agency.js +112 -1
- package/dist/cjs/applicationRoles.js +211 -0
- package/dist/cjs/client/check.js +9 -0
- package/dist/cjs/client/contentClaims.js +139 -0
- package/dist/cjs/client/index.js +37 -1
- package/dist/cjs/client/safety.js +183 -2
- package/dist/cjs/client/signIn.js +2 -0
- package/dist/cjs/contentClaims.js +246 -0
- package/dist/cjs/index.js +14 -3
- package/dist/cjs/safety.js +430 -1
- package/dist/cjs/serviceToken.js +14 -0
- package/dist/esm/accountAccess.js +69 -0
- package/dist/esm/agency.js +111 -0
- package/dist/esm/applicationRoles.js +207 -0
- package/dist/esm/client/check.js +8 -0
- package/dist/esm/client/contentClaims.js +134 -0
- package/dist/esm/client/index.js +2 -1
- package/dist/esm/client/safety.js +183 -2
- package/dist/esm/client/signIn.js +2 -0
- package/dist/esm/contentClaims.js +229 -0
- package/dist/esm/index.js +5 -1
- package/dist/esm/safety.js +423 -0
- package/dist/esm/serviceToken.js +11 -0
- package/dist/types/accountAccess.d.ts +128 -0
- package/dist/types/accountEmail.d.ts +2 -2
- package/dist/types/agency.d.ts +327 -2
- package/dist/types/agentKeys.d.ts +6 -6
- package/dist/types/applicationRoles.d.ts +438 -0
- package/dist/types/client/agentKeys.d.ts +2 -2
- package/dist/types/client/check.d.ts +3 -0
- package/dist/types/client/contentClaims.d.ts +106 -0
- package/dist/types/client/deviceSession.d.ts +1 -1
- package/dist/types/client/index.d.ts +2 -1
- package/dist/types/client/safety.d.ts +204 -4
- package/dist/types/client/signIn.d.ts +1 -1
- package/dist/types/contentClaims.d.ts +383 -0
- package/dist/types/deviceSession.d.ts +4 -4
- package/dist/types/email/messages.d.ts +4 -4
- package/dist/types/externalIdentity.d.ts +16 -16
- package/dist/types/index.d.ts +6 -2
- package/dist/types/inference/accountBilling.d.ts +2 -2
- package/dist/types/inference/aliaModelRelease.d.ts +8 -8
- package/dist/types/inference/catalogue.d.ts +26 -26
- package/dist/types/inference/inbox.d.ts +4 -4
- package/dist/types/inference/modelDocumentation.d.ts +16 -16
- package/dist/types/inference/providerConnection.d.ts +4 -4
- package/dist/types/inference/realtime.d.ts +74 -74
- package/dist/types/inference/request.d.ts +60 -60
- package/dist/types/inference/usage.d.ts +4 -4
- package/dist/types/products/access.d.ts +4 -4
- package/dist/types/safety.d.ts +2374 -2
- package/dist/types/serviceToken.d.ts +11 -0
- package/dist/types/sessionStatus.d.ts +4 -4
- package/dist/types/signIn.d.ts +5 -5
- package/dist/types/updates.d.ts +6 -6
- package/dist/types/userResponse.d.ts +8 -8
- package/package.json +5 -20
- package/dist/cjs/.tsbuildinfo +0 -1
- package/dist/esm/.tsbuildinfo +0 -1
- package/dist/types/.tsbuildinfo +0 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Wire contract for `GET /internal/accounts/:accountId/access/:userId`: Oxy
|
|
4
|
+
* answers a trusted first-party service's question "may this person act FOR
|
|
5
|
+
* that account, and with what?" with no session of that person in the request.
|
|
6
|
+
*
|
|
7
|
+
* It exists for the account Oxy never lets anybody act AS: a `channel`
|
|
8
|
+
* ({@link isDelegatedActAsEligibleKind} refuses it). An app publishes to a
|
|
9
|
+
* channel with ITS OWN credential, on behalf of a member Oxy vouches for here,
|
|
10
|
+
* so nobody holds a bearer that speaks as the channel. A long-running import
|
|
11
|
+
* (Oxy Move) has only a service token and `X-Oxy-User-Id`, which the
|
|
12
|
+
* user-facing `GET /accounts/:id/members` refuses; this is its answer.
|
|
13
|
+
*
|
|
14
|
+
* Needs a service token whose application is platform-trusted AND holds the
|
|
15
|
+
* privileged {@link ACCOUNTS_MEMBERSHIP_READ_SCOPE}. The answer is always 200:
|
|
16
|
+
* `access` is `null` for no active membership, an archived account, an unknown
|
|
17
|
+
* account and an unclaimed directory account alike. Callers must treat every
|
|
18
|
+
* other status as a refusal (fail closed), and must not cache the answer longer
|
|
19
|
+
* than the action it authorizes.
|
|
20
|
+
*
|
|
21
|
+
* Platform-agnostic — zod only.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.accountAccessVerdictSchema = exports.accountAccessGrantSchema = exports.accountAccessSourceSchema = exports.ACCOUNT_ACCESS_SOURCES = exports.ACCOUNTS_MEMBERSHIP_READ_SCOPE = void 0;
|
|
25
|
+
const zod_1 = require("zod");
|
|
26
|
+
const accountGraph_1 = require("./accountGraph");
|
|
27
|
+
/**
|
|
28
|
+
* The scope the endpoint requires.
|
|
29
|
+
*
|
|
30
|
+
* Privileged (staff-granted). Channel membership is sensitive because a channel
|
|
31
|
+
* is anonymous: its members are the people behind a voice that is published
|
|
32
|
+
* without their names, and "is this person a member of that channel" is
|
|
33
|
+
* exactly the fact the channel exists to withhold.
|
|
34
|
+
*/
|
|
35
|
+
exports.ACCOUNTS_MEMBERSHIP_READ_SCOPE = 'accounts:membership:read';
|
|
36
|
+
/**
|
|
37
|
+
* Where the access comes from.
|
|
38
|
+
*
|
|
39
|
+
* - `self` — the person IS the account (a personal account asked about itself).
|
|
40
|
+
* - `direct` — an active membership row on the account.
|
|
41
|
+
* - `inherited` — an active, cascading membership on an ancestor account.
|
|
42
|
+
*/
|
|
43
|
+
exports.ACCOUNT_ACCESS_SOURCES = ['self', 'direct', 'inherited'];
|
|
44
|
+
exports.accountAccessSourceSchema = zod_1.z.enum(exports.ACCOUNT_ACCESS_SOURCES);
|
|
45
|
+
/**
|
|
46
|
+
* One person's EFFECTIVE access over one account.
|
|
47
|
+
*
|
|
48
|
+
* `role` and `permissions` are strings rather than closed enums on purpose: the
|
|
49
|
+
* role and permission vocabularies are the API's, they grow, and a consumer
|
|
50
|
+
* pinned to an older contracts release must keep reading new ones. Gate on
|
|
51
|
+
* `permissions` (the role's baseline adjusted by per-member grants and
|
|
52
|
+
* revokes), never on `role`.
|
|
53
|
+
*/
|
|
54
|
+
exports.accountAccessGrantSchema = zod_1.z.object({
|
|
55
|
+
role: zod_1.z.string().min(1),
|
|
56
|
+
permissions: zod_1.z.array(zod_1.z.string().min(1)),
|
|
57
|
+
/** Only an active membership grants access, so this is the one value. */
|
|
58
|
+
status: zod_1.z.literal('active'),
|
|
59
|
+
source: exports.accountAccessSourceSchema,
|
|
60
|
+
});
|
|
61
|
+
exports.accountAccessVerdictSchema = zod_1.z
|
|
62
|
+
.object({
|
|
63
|
+
accountId: zod_1.z.string().min(1),
|
|
64
|
+
/** `null` when no such account exists. */
|
|
65
|
+
kind: accountGraph_1.accountKindSchema.nullable(),
|
|
66
|
+
/** `null` means: this person may not act for this account. */
|
|
67
|
+
access: exports.accountAccessGrantSchema.nullable(),
|
|
68
|
+
})
|
|
69
|
+
.refine((verdict) => verdict.access === null || verdict.kind !== null, {
|
|
70
|
+
message: 'An account that does not exist grants no access',
|
|
71
|
+
path: ['access'],
|
|
72
|
+
});
|
package/dist/cjs/agency.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normalizedAppEventSchema = exports.catalogRegistrationSchema = exports.appCapabilityCatalogSchema = exports.catalogEventSchema = exports.catalogToolSchema = exports.auditEventSchema = exports.auditResultSchema = exports.policyDecisionSchema = exports.invocationPrincipalSchema = exports.capabilityTicketClaimsSchema = exports.automationDefinitionSchema = exports.automationDataFlowSchema = exports.automationActorSelectionSchema = exports.automationTriggerSchema = exports.delegationGrantSchema = exports.capabilityTicketRequestSchema = exports.capabilityCoordinatorSchema = exports.executionAuthorizationRefSchema = exports.capabilityCatalogBindingSchema = exports.grantLimitSchema = exports.toolGrantOverrideSchema = exports.resourceRefSchema = exports.foregroundExecutionAuthorizationInputSchema = exports.executionActorRefSchema = exports.actorRefSchema = exports.capabilityPackageSchema = exports.CAPABILITY_PACKAGES = exports.autonomyLevelSchema = exports.AUTONOMY_LEVELS = void 0;
|
|
3
|
+
exports.normalizedAppEventSchema = exports.catalogRegistrationSchema = exports.appCapabilityCatalogSchema = exports.CATALOG_INTERNAL_TRANSPORTS = exports.catalogFeatureSchema = exports.catalogEventSchema = exports.catalogToolSchema = exports.auditEventSchema = exports.auditResultSchema = exports.policyDecisionSchema = exports.invocationPrincipalSchema = exports.capabilityTicketClaimsSchema = exports.automationDefinitionSchema = exports.automationDataFlowSchema = exports.automationActorSelectionSchema = exports.automationTriggerSchema = exports.agentRequesterCeilingSchema = exports.agentRequesterCeilingWriteSchema = exports.AGENT_REQUESTER_CEILING_AUTONOMY = exports.delegationGrantSchema = exports.capabilityTicketRequestSchema = exports.capabilityCoordinatorSchema = exports.executionAuthorizationRefSchema = exports.capabilityCatalogBindingSchema = exports.grantLimitSchema = exports.toolGrantOverrideSchema = exports.resourceRefSchema = exports.foregroundExecutionAuthorizationInputSchema = exports.executionActorRefSchema = exports.actorRefSchema = exports.capabilityPackageSchema = exports.CAPABILITY_PACKAGES = exports.autonomyLevelSchema = exports.AUTONOMY_LEVELS = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const capabilityInvocation_1 = require("./capabilityInvocation");
|
|
6
6
|
const nonEmptyStringSchema = zod_1.z.string().trim().min(1);
|
|
@@ -143,6 +143,35 @@ exports.delegationGrantSchema = zod_1.z
|
|
|
143
143
|
updatedAt: zod_1.z.string().datetime(),
|
|
144
144
|
})
|
|
145
145
|
.strict();
|
|
146
|
+
/**
|
|
147
|
+
* The two levels an owner publishes for whoever uses their agent (ADR 0042):
|
|
148
|
+
* "Ver" and "Ver y actuar". A ceiling never says `draft` or `execute_on_request`:
|
|
149
|
+
* an agent is either allowed to act on its own or not at all, and nothing ever
|
|
150
|
+
* asks the person for approval.
|
|
151
|
+
*/
|
|
152
|
+
exports.AGENT_REQUESTER_CEILING_AUTONOMY = ['read_only', 'autonomous'];
|
|
153
|
+
/** What an agent may do in one app for whoever uses it; written by an operator of the bot. */
|
|
154
|
+
exports.agentRequesterCeilingWriteSchema = zod_1.z
|
|
155
|
+
.object({
|
|
156
|
+
capabilityPackages: zod_1.z.array(exports.capabilityPackageSchema),
|
|
157
|
+
capabilities: zod_1.z.array(identifierSchema).default([]),
|
|
158
|
+
toolOverrides: zod_1.z.array(exports.toolGrantOverrideSchema).default([]),
|
|
159
|
+
limits: zod_1.z.array(exports.grantLimitSchema).default([]),
|
|
160
|
+
maximumAutonomy: zod_1.z.enum(exports.AGENT_REQUESTER_CEILING_AUTONOMY),
|
|
161
|
+
})
|
|
162
|
+
.strict();
|
|
163
|
+
exports.agentRequesterCeilingSchema = exports.agentRequesterCeilingWriteSchema
|
|
164
|
+
.extend({
|
|
165
|
+
id: identifierSchema,
|
|
166
|
+
agentAccountId: identifierSchema,
|
|
167
|
+
appId: identifierSchema,
|
|
168
|
+
catalog: exports.capabilityCatalogBindingSchema,
|
|
169
|
+
version: zod_1.z.number().int().positive(),
|
|
170
|
+
revokedAt: zod_1.z.string().datetime().nullable(),
|
|
171
|
+
createdAt: zod_1.z.string().datetime(),
|
|
172
|
+
updatedAt: zod_1.z.string().datetime(),
|
|
173
|
+
})
|
|
174
|
+
.strict();
|
|
146
175
|
exports.automationTriggerSchema = zod_1.z.discriminatedUnion('type', [
|
|
147
176
|
zod_1.z.object({ type: zod_1.z.literal('manual') }).strict(),
|
|
148
177
|
zod_1.z
|
|
@@ -204,6 +233,8 @@ exports.capabilityTicketClaimsSchema = zod_1.z
|
|
|
204
233
|
/** Signed only for callers that explicitly requested a pinned catalogue. */
|
|
205
234
|
catalog: exports.capabilityCatalogBindingSchema.optional(),
|
|
206
235
|
grantId: identifierSchema.optional(),
|
|
236
|
+
/** The owner's published ceiling that bounds an agent acting for a non-owner (ADR 0042). */
|
|
237
|
+
ceilingId: identifierSchema.optional(),
|
|
207
238
|
requesterAccountId: identifierSchema,
|
|
208
239
|
ownerAccountId: identifierSchema,
|
|
209
240
|
actor: exports.executionActorRefSchema,
|
|
@@ -242,6 +273,24 @@ exports.capabilityTicketClaimsSchema = zod_1.z
|
|
|
242
273
|
message: 'requester actors require their exact principal and direct read-only authority without standing grants',
|
|
243
274
|
});
|
|
244
275
|
}
|
|
276
|
+
if (claims.ceilingId !== undefined) {
|
|
277
|
+
if (claims.grantId !== undefined) {
|
|
278
|
+
context.addIssue({
|
|
279
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
280
|
+
path: ['ceilingId'],
|
|
281
|
+
message: 'a ticket is bounded by either a grant or a ceiling, never both',
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
if (claims.actor.type !== 'agent' ||
|
|
285
|
+
claims.ownerAccountId !== claims.requesterAccountId ||
|
|
286
|
+
claims.resource.resourceId !== claims.resource.effectiveAccountId) {
|
|
287
|
+
context.addIssue({
|
|
288
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
289
|
+
path: ['ceilingId'],
|
|
290
|
+
message: "a ceiling bounds an agent acting on the requester's own authority at an account root",
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
245
294
|
if (claims.exp <= claims.iat) {
|
|
246
295
|
context.addIssue({
|
|
247
296
|
code: zod_1.z.ZodIssueCode.custom,
|
|
@@ -303,6 +352,7 @@ exports.policyDecisionSchema = zod_1.z
|
|
|
303
352
|
reason: identifierSchema,
|
|
304
353
|
effectiveAutonomy: exports.autonomyLevelSchema.optional(),
|
|
305
354
|
grantId: identifierSchema.optional(),
|
|
355
|
+
ceilingId: identifierSchema.optional(),
|
|
306
356
|
})
|
|
307
357
|
.strict();
|
|
308
358
|
exports.auditResultSchema = zod_1.z
|
|
@@ -512,6 +562,24 @@ exports.catalogEventSchema = zod_1.z
|
|
|
512
562
|
resourceTypes: zod_1.z.array(identifierSchema).min(1),
|
|
513
563
|
})
|
|
514
564
|
.strict();
|
|
565
|
+
/**
|
|
566
|
+
* A feature an agent may gate on (Alia ADR 0020 §6): the app answers whether the
|
|
567
|
+
* person may use it (`checkTool`) and, when using it costs something, spends it
|
|
568
|
+
* (`consumeTool`). Both are internal tools of the same catalogue.
|
|
569
|
+
*/
|
|
570
|
+
exports.catalogFeatureSchema = zod_1.z
|
|
571
|
+
.object({
|
|
572
|
+
key: zod_1.z
|
|
573
|
+
.string()
|
|
574
|
+
.regex(/^[a-z][a-z0-9_-]*(?:\.[a-z][a-z0-9_-]*)*$/, 'feature keys are dot-separated slugs')
|
|
575
|
+
.max(128),
|
|
576
|
+
description: nonEmptyStringSchema,
|
|
577
|
+
checkTool: identifierSchema,
|
|
578
|
+
consumeTool: identifierSchema.optional(),
|
|
579
|
+
})
|
|
580
|
+
.strict();
|
|
581
|
+
/** How a coordinator reaches the catalogue's internal tools. */
|
|
582
|
+
exports.CATALOG_INTERNAL_TRANSPORTS = ['http', 'mcp'];
|
|
515
583
|
exports.appCapabilityCatalogSchema = zod_1.z
|
|
516
584
|
.object({
|
|
517
585
|
schemaVersion: zod_1.z.literal('1'),
|
|
@@ -567,11 +635,54 @@ exports.appCapabilityCatalogSchema = zod_1.z
|
|
|
567
635
|
})
|
|
568
636
|
.strict()
|
|
569
637
|
.optional(),
|
|
638
|
+
/**
|
|
639
|
+
* `mcp`: internal tools are served at `<internalBaseUrl>/_oxy/mcp`. Absent or
|
|
640
|
+
* `http`: each tool's `invocation` path on `internalBaseUrl`.
|
|
641
|
+
*/
|
|
642
|
+
internalTransport: zod_1.z.enum(exports.CATALOG_INTERNAL_TRANSPORTS).optional(),
|
|
643
|
+
features: zod_1.z.array(exports.catalogFeatureSchema).optional(),
|
|
570
644
|
tools: zod_1.z.array(exports.catalogToolSchema),
|
|
571
645
|
events: zod_1.z.array(exports.catalogEventSchema),
|
|
572
646
|
})
|
|
573
647
|
.strict()
|
|
574
648
|
.superRefine((catalog, context) => {
|
|
649
|
+
const toolsByName = new Map(catalog.tools.map((tool) => [tool.name, tool]));
|
|
650
|
+
const featureKeys = new Set();
|
|
651
|
+
for (const [index, feature] of (catalog.features ?? []).entries()) {
|
|
652
|
+
if (featureKeys.has(feature.key)) {
|
|
653
|
+
context.addIssue({
|
|
654
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
655
|
+
message: `Duplicate feature key: ${feature.key}`,
|
|
656
|
+
path: ['features', index, 'key'],
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
featureKeys.add(feature.key);
|
|
660
|
+
const check = toolsByName.get(feature.checkTool);
|
|
661
|
+
if (!check ||
|
|
662
|
+
check.effect !== 'read' ||
|
|
663
|
+
check.exposure.length !== 1 ||
|
|
664
|
+
check.exposure[0] !== 'internal') {
|
|
665
|
+
context.addIssue({
|
|
666
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
667
|
+
message: 'checkTool must name an internal-only read tool of this catalogue',
|
|
668
|
+
path: ['features', index, 'checkTool'],
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
if (feature.consumeTool !== undefined) {
|
|
672
|
+
const consume = toolsByName.get(feature.consumeTool);
|
|
673
|
+
if (!consume ||
|
|
674
|
+
consume.effect === 'read' ||
|
|
675
|
+
consume.idempotency !== 'required' ||
|
|
676
|
+
consume.exposure.length !== 1 ||
|
|
677
|
+
consume.exposure[0] !== 'internal') {
|
|
678
|
+
context.addIssue({
|
|
679
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
680
|
+
message: 'consumeTool must name an internal-only effectful tool that requires idempotency keys',
|
|
681
|
+
path: ['features', index, 'consumeTool'],
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
575
686
|
const names = new Set();
|
|
576
687
|
for (const [index, tool] of catalog.tools.entries()) {
|
|
577
688
|
if (names.has(tool.name)) {
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Application roles — the wire contract for permissions an APPLICATION declares
|
|
4
|
+
* about its own users, and for the grants that give them to Oxy users.
|
|
5
|
+
*
|
|
6
|
+
* Oxy decides who holds an app's operator powers (who may run Mercaria's
|
|
7
|
+
* payments desk, moderate GoWay, administer Homiio), so no app keeps an
|
|
8
|
+
* allow-list of Oxy user ids in its environment. The app declares a closed
|
|
9
|
+
* vocabulary of keys, its owners grant them to people, and the app's backend
|
|
10
|
+
* asks Oxy which of them the human behind a request holds
|
|
11
|
+
* (`GET /applications/:appId/roles/me`, read through `@oxy.so/core/server`).
|
|
12
|
+
*
|
|
13
|
+
* ## Why an application key can never collide with an Oxy permission
|
|
14
|
+
*
|
|
15
|
+
* Every permission Oxy itself checks (`app:update`, `apps:create`,
|
|
16
|
+
* `credentials:rotate`, every scope) is spelled with a COLON. An application key
|
|
17
|
+
* is dot-separated lowercase segments and cannot contain one, so the two
|
|
18
|
+
* vocabularies are disjoint by syntax, not by convention. They are also stored
|
|
19
|
+
* apart (scoped to one application id) and no Oxy gate ever reads an
|
|
20
|
+
* application key. A key means something only to the application that declared
|
|
21
|
+
* it.
|
|
22
|
+
*
|
|
23
|
+
* The pattern is exported as a SOURCE string because the API renders the same
|
|
24
|
+
* text into a Postgres CHECK; JavaScript's `RegExp` and Postgres' ARE agree on
|
|
25
|
+
* everything it uses (anchors, classes, a capturing group, a bounded repeat).
|
|
26
|
+
*
|
|
27
|
+
* Platform-agnostic — zod only, no react/react-native/expo.
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.applicationRolesMeSchema = exports.listApplicationRoleGrantsQuerySchema = exports.revokeApplicationRoleGrantRequestSchema = exports.createApplicationRoleGrantRequestSchema = exports.applicationRoleGrantListSchema = exports.applicationRoleGrantSchema = exports.declareApplicationPermissionsResponseSchema = exports.applicationPermissionListSchema = exports.applicationPermissionSchema = exports.declareApplicationPermissionsRequestSchema = exports.applicationPermissionDeclarationSchema = exports.applicationPermissionKeySchema = exports.APPLICATION_ROLE_ERROR_CODES = exports.APPLICATION_ROLE_GRANTS_PAGE_MAX = exports.APPLICATION_ROLE_GRANTS_PAGE_DEFAULT = exports.APPLICATION_ROLE_REASON_MAX_LENGTH = exports.APPLICATION_PERMISSIONS_MAX_DECLARED = exports.APPLICATION_PERMISSION_DESCRIPTION_MAX_LENGTH = exports.APPLICATION_PERMISSION_KEY_MAX_LENGTH = exports.APPLICATION_PERMISSION_KEY_PATTERN = exports.APPLICATION_PERMISSION_KEY_PATTERN_SOURCE = void 0;
|
|
31
|
+
exports.isApplicationPermissionKey = isApplicationPermissionKey;
|
|
32
|
+
const zod_1 = require("zod");
|
|
33
|
+
/**
|
|
34
|
+
* Two to four dot-separated segments, each a lowercase letter followed by
|
|
35
|
+
* lowercase letters, digits or underscores: `payments.operate`,
|
|
36
|
+
* `catalog.review.publish`. At least two segments so a key always names an
|
|
37
|
+
* area and an action; never a colon (see the header).
|
|
38
|
+
*/
|
|
39
|
+
exports.APPLICATION_PERMISSION_KEY_PATTERN_SOURCE = '^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*){1,3}$';
|
|
40
|
+
exports.APPLICATION_PERMISSION_KEY_PATTERN = new RegExp(exports.APPLICATION_PERMISSION_KEY_PATTERN_SOURCE);
|
|
41
|
+
exports.APPLICATION_PERMISSION_KEY_MAX_LENGTH = 64;
|
|
42
|
+
exports.APPLICATION_PERMISSION_DESCRIPTION_MAX_LENGTH = 280;
|
|
43
|
+
/** How many live keys one application may declare. Operator powers are few. */
|
|
44
|
+
exports.APPLICATION_PERMISSIONS_MAX_DECLARED = 64;
|
|
45
|
+
exports.APPLICATION_ROLE_REASON_MAX_LENGTH = 500;
|
|
46
|
+
exports.APPLICATION_ROLE_GRANTS_PAGE_DEFAULT = 50;
|
|
47
|
+
exports.APPLICATION_ROLE_GRANTS_PAGE_MAX = 200;
|
|
48
|
+
/**
|
|
49
|
+
* The `error` codes the application-roles routes answer beyond the generic
|
|
50
|
+
* ones (`BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`). Renaming one
|
|
51
|
+
* is a breaking change.
|
|
52
|
+
*/
|
|
53
|
+
exports.APPLICATION_ROLE_ERROR_CODES = {
|
|
54
|
+
/** 403: a grant naming yourself on an application you do not own outright. */
|
|
55
|
+
selfGrantForbidden: 'SELF_GRANT_FORBIDDEN',
|
|
56
|
+
/** 422: the key is not a live declared permission of this application. */
|
|
57
|
+
unknownPermission: 'UNKNOWN_PERMISSION',
|
|
58
|
+
/** 422: the grantee is not a person (an organization, project, bot or channel). */
|
|
59
|
+
granteeNotAPerson: 'GRANTEE_NOT_A_PERSON',
|
|
60
|
+
/** 409: the grantee already holds the key; `details.grantId` names the live grant. */
|
|
61
|
+
alreadyGranted: 'ALREADY_GRANTED',
|
|
62
|
+
/** 409: the grant was already revoked. */
|
|
63
|
+
alreadyRevoked: 'ALREADY_REVOKED',
|
|
64
|
+
/** 403: the bearer's session belongs to a DIFFERENT application. */
|
|
65
|
+
sessionApplicationMismatch: 'SESSION_APPLICATION_MISMATCH',
|
|
66
|
+
};
|
|
67
|
+
/** Whether `value` is a well-formed application permission key. */
|
|
68
|
+
function isApplicationPermissionKey(value) {
|
|
69
|
+
return (typeof value === 'string' &&
|
|
70
|
+
value.length <= exports.APPLICATION_PERMISSION_KEY_MAX_LENGTH &&
|
|
71
|
+
exports.APPLICATION_PERMISSION_KEY_PATTERN.test(value));
|
|
72
|
+
}
|
|
73
|
+
exports.applicationPermissionKeySchema = zod_1.z
|
|
74
|
+
.string()
|
|
75
|
+
.max(exports.APPLICATION_PERMISSION_KEY_MAX_LENGTH)
|
|
76
|
+
.regex(exports.APPLICATION_PERMISSION_KEY_PATTERN, 'A permission key is 2-4 dot-separated lowercase segments, e.g. payments.operate');
|
|
77
|
+
/** Plain text: no control characters other than a newline or a tab. */
|
|
78
|
+
function plainText(max) {
|
|
79
|
+
return zod_1.z
|
|
80
|
+
.string()
|
|
81
|
+
.trim()
|
|
82
|
+
.min(1)
|
|
83
|
+
.max(max)
|
|
84
|
+
.refine((value) => Array.from(value).every((character) => {
|
|
85
|
+
const code = character.charCodeAt(0);
|
|
86
|
+
return (code >= 32 && code !== 127) || character === '\n' || character === '\t';
|
|
87
|
+
}), 'Must be plain text');
|
|
88
|
+
}
|
|
89
|
+
const id = zod_1.z.string().min(1).max(128);
|
|
90
|
+
const isoTimestamp = zod_1.z.string().min(1);
|
|
91
|
+
const reasonSchema = plainText(exports.APPLICATION_ROLE_REASON_MAX_LENGTH);
|
|
92
|
+
/** One key as the application's owners declare it. */
|
|
93
|
+
exports.applicationPermissionDeclarationSchema = zod_1.z
|
|
94
|
+
.object({
|
|
95
|
+
key: exports.applicationPermissionKeySchema,
|
|
96
|
+
description: plainText(exports.APPLICATION_PERMISSION_DESCRIPTION_MAX_LENGTH),
|
|
97
|
+
})
|
|
98
|
+
.strict();
|
|
99
|
+
/**
|
|
100
|
+
* `PUT /applications/:appId/roles/permissions` — the WHOLE vocabulary.
|
|
101
|
+
*
|
|
102
|
+
* Declarative: a key present here and not live is added, a live key absent
|
|
103
|
+
* here is retired (and every grant of it revoked), a live key present here
|
|
104
|
+
* keeps its identity and takes the new description. Sending the same body twice
|
|
105
|
+
* changes nothing.
|
|
106
|
+
*/
|
|
107
|
+
exports.declareApplicationPermissionsRequestSchema = zod_1.z
|
|
108
|
+
.object({
|
|
109
|
+
permissions: zod_1.z
|
|
110
|
+
.array(exports.applicationPermissionDeclarationSchema)
|
|
111
|
+
.max(exports.APPLICATION_PERMISSIONS_MAX_DECLARED),
|
|
112
|
+
})
|
|
113
|
+
.strict()
|
|
114
|
+
.superRefine((value, ctx) => {
|
|
115
|
+
const seen = new Set();
|
|
116
|
+
value.permissions.forEach((permission, index) => {
|
|
117
|
+
if (seen.has(permission.key)) {
|
|
118
|
+
ctx.addIssue({
|
|
119
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
120
|
+
path: ['permissions', index, 'key'],
|
|
121
|
+
message: `Duplicate permission key ${permission.key}`,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
seen.add(permission.key);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
/** A declared key. Retired keys are history: they confer nothing. */
|
|
128
|
+
exports.applicationPermissionSchema = zod_1.z.object({
|
|
129
|
+
key: exports.applicationPermissionKeySchema,
|
|
130
|
+
description: zod_1.z.string(),
|
|
131
|
+
declaredAt: isoTimestamp,
|
|
132
|
+
declaredByUserId: id.nullable(),
|
|
133
|
+
retiredAt: isoTimestamp.nullable(),
|
|
134
|
+
retiredByUserId: id.nullable(),
|
|
135
|
+
});
|
|
136
|
+
exports.applicationPermissionListSchema = zod_1.z.object({
|
|
137
|
+
items: zod_1.z.array(exports.applicationPermissionSchema),
|
|
138
|
+
});
|
|
139
|
+
exports.declareApplicationPermissionsResponseSchema = exports.applicationPermissionListSchema.extend({
|
|
140
|
+
/** Keys that became live with this declaration. */
|
|
141
|
+
added: zod_1.z.array(exports.applicationPermissionKeySchema),
|
|
142
|
+
/** Keys this declaration retired. */
|
|
143
|
+
retired: zod_1.z.array(exports.applicationPermissionKeySchema),
|
|
144
|
+
/** Live keys whose description changed. */
|
|
145
|
+
described: zod_1.z.array(exports.applicationPermissionKeySchema),
|
|
146
|
+
/** Live grants revoked because their key was retired. */
|
|
147
|
+
revokedGrants: zod_1.z.number().int().nonnegative(),
|
|
148
|
+
});
|
|
149
|
+
/** One grant of one key to one Oxy user. Revocation is a stamp, never a delete. */
|
|
150
|
+
exports.applicationRoleGrantSchema = zod_1.z.object({
|
|
151
|
+
id,
|
|
152
|
+
applicationId: id,
|
|
153
|
+
permission: exports.applicationPermissionKeySchema,
|
|
154
|
+
userId: id,
|
|
155
|
+
/** The grantee's handle, for display. */
|
|
156
|
+
username: zod_1.z.string().nullable(),
|
|
157
|
+
/** The HUMAN who granted it; null once that account was erased. */
|
|
158
|
+
grantedByUserId: id.nullable(),
|
|
159
|
+
grantedAt: isoTimestamp,
|
|
160
|
+
grantReason: zod_1.z.string().nullable(),
|
|
161
|
+
revokedAt: isoTimestamp.nullable(),
|
|
162
|
+
revokedByUserId: id.nullable(),
|
|
163
|
+
revokeReason: zod_1.z.string().nullable(),
|
|
164
|
+
});
|
|
165
|
+
exports.applicationRoleGrantListSchema = zod_1.z.object({
|
|
166
|
+
items: zod_1.z.array(exports.applicationRoleGrantSchema),
|
|
167
|
+
nextCursor: zod_1.z.string().nullable(),
|
|
168
|
+
});
|
|
169
|
+
/** `POST /applications/:appId/roles/grants` — name the grantee by id OR handle. */
|
|
170
|
+
exports.createApplicationRoleGrantRequestSchema = zod_1.z
|
|
171
|
+
.object({
|
|
172
|
+
userId: id.optional(),
|
|
173
|
+
username: zod_1.z.string().trim().min(1).max(64).optional(),
|
|
174
|
+
permission: exports.applicationPermissionKeySchema,
|
|
175
|
+
reason: reasonSchema.optional(),
|
|
176
|
+
})
|
|
177
|
+
.strict()
|
|
178
|
+
.refine((value) => (value.userId === undefined) !== (value.username === undefined), {
|
|
179
|
+
message: 'Name the grantee with exactly one of userId or username',
|
|
180
|
+
path: ['userId'],
|
|
181
|
+
});
|
|
182
|
+
/** `POST /applications/:appId/roles/grants/:grantId/revoke`. */
|
|
183
|
+
exports.revokeApplicationRoleGrantRequestSchema = zod_1.z
|
|
184
|
+
.object({
|
|
185
|
+
reason: reasonSchema.optional(),
|
|
186
|
+
})
|
|
187
|
+
.strict();
|
|
188
|
+
/** `GET /applications/:appId/roles/grants` query. */
|
|
189
|
+
exports.listApplicationRoleGrantsQuerySchema = zod_1.z
|
|
190
|
+
.object({
|
|
191
|
+
permission: exports.applicationPermissionKeySchema.optional(),
|
|
192
|
+
userId: id.optional(),
|
|
193
|
+
includeRevoked: zod_1.z
|
|
194
|
+
.enum(['true', 'false'])
|
|
195
|
+
.transform((value) => value === 'true')
|
|
196
|
+
.optional(),
|
|
197
|
+
limit: zod_1.z.coerce.number().int().min(1).max(exports.APPLICATION_ROLE_GRANTS_PAGE_MAX).optional(),
|
|
198
|
+
cursor: zod_1.z.string().min(1).max(512).optional(),
|
|
199
|
+
})
|
|
200
|
+
.strict();
|
|
201
|
+
/**
|
|
202
|
+
* `GET /applications/:appId/roles/me` — the keys the HUMAN behind the bearer
|
|
203
|
+
* holds for this application, in no particular order. `userId` is that human
|
|
204
|
+
* (the operator), which is NOT the account the session speaks as when a person
|
|
205
|
+
* is acting as an organization.
|
|
206
|
+
*/
|
|
207
|
+
exports.applicationRolesMeSchema = zod_1.z.object({
|
|
208
|
+
applicationId: id,
|
|
209
|
+
userId: id,
|
|
210
|
+
permissions: zod_1.z.array(exports.applicationPermissionKeySchema),
|
|
211
|
+
});
|
package/dist/cjs/client/check.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* strictObject(shape) unknown keys FAIL (`.strict()`)
|
|
26
26
|
* looseObject(shape) unknown keys are KEPT (`.passthrough()`)
|
|
27
27
|
* refine(c, test) a further condition, run once `c` passes
|
|
28
|
+
* transform(c, f) `f` rewrites the value once `c` passes
|
|
28
29
|
* union / discriminatedUnion as Zod 3 reports them
|
|
29
30
|
*
|
|
30
31
|
* A failure carries the first issue Zod would report and whether Zod would have
|
|
@@ -52,6 +53,7 @@ exports.nullable = nullable;
|
|
|
52
53
|
exports.withDefault = withDefault;
|
|
53
54
|
exports.refine = refine;
|
|
54
55
|
exports.refineAt = refineAt;
|
|
56
|
+
exports.transform = transform;
|
|
55
57
|
exports.branded = branded;
|
|
56
58
|
exports.minItems = minItems;
|
|
57
59
|
exports.maxItems = maxItems;
|
|
@@ -265,6 +267,13 @@ function refineAt(inner, test) {
|
|
|
265
267
|
: checked;
|
|
266
268
|
};
|
|
267
269
|
}
|
|
270
|
+
/** Zod's `.transform()`: rewrites a value `inner` accepted. */
|
|
271
|
+
function transform(inner, rewrite) {
|
|
272
|
+
return (input, path) => {
|
|
273
|
+
const checked = inner(input, path);
|
|
274
|
+
return checked.ok ? ok(rewrite(checked.value)) : checked;
|
|
275
|
+
};
|
|
276
|
+
}
|
|
268
277
|
/** Narrows a checked value to a branded contract type (Zod's `.brand()`). */
|
|
269
278
|
function branded(inner) {
|
|
270
279
|
return inner;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Content claims (ADR 0043 D10) — the constants of the public "Report content
|
|
4
|
+
* / rights claim" intake and of a claim's review, without Zod. Schemas:
|
|
5
|
+
* `../contentClaims.ts`.
|
|
6
|
+
*
|
|
7
|
+
* Part of `@oxy.so/contracts/client`: never import `zod` here (see `./check.ts`).
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.COUNTER_NOTICE_STATEMENT_VERSION = exports.COUNTER_NOTICE_STATEMENTS = exports.CONTENT_CLAIM_COUNTER_NOTICE_LIMITS = exports.CONTENT_CLAIM_REFERENCE_PATTERN = exports.CONTENT_CLAIM_ERROR_CODES = exports.ERASURE_REQUEST_DEADLINE_DAYS = exports.CLOSED_CONTENT_CLAIM_STATES = exports.OPEN_CONTENT_CLAIM_STATES = exports.CONTENT_CLAIM_STATES = exports.CONTENT_CLAIM_LIMITS = exports.CLAIMANT_RELATIONSHIPS = exports.CONTENT_CLAIM_KINDS = void 0;
|
|
11
|
+
exports.isRightsClaim = isRightsClaim;
|
|
12
|
+
exports.isErasureRequest = isErasureRequest;
|
|
13
|
+
/** What a claim asserts. */
|
|
14
|
+
exports.CONTENT_CLAIM_KINDS = [
|
|
15
|
+
'copyright',
|
|
16
|
+
'trademark',
|
|
17
|
+
'privacy',
|
|
18
|
+
'illegal_content',
|
|
19
|
+
'other',
|
|
20
|
+
];
|
|
21
|
+
/** Who the claimant is to the content. */
|
|
22
|
+
exports.CLAIMANT_RELATIONSHIPS = [
|
|
23
|
+
'rights_holder',
|
|
24
|
+
'authorized_agent',
|
|
25
|
+
'affected_person',
|
|
26
|
+
'other',
|
|
27
|
+
];
|
|
28
|
+
/** The bounds of a submission. The website fills its validation messages from these. */
|
|
29
|
+
exports.CONTENT_CLAIM_LIMITS = {
|
|
30
|
+
minUrls: 1,
|
|
31
|
+
maxUrls: 20,
|
|
32
|
+
maxUrlLength: 2048,
|
|
33
|
+
minDescription: 20,
|
|
34
|
+
maxDescription: 5000,
|
|
35
|
+
maxName: 200,
|
|
36
|
+
maxOrganization: 200,
|
|
37
|
+
maxEmail: 254,
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Where a claim is in its review.
|
|
41
|
+
*
|
|
42
|
+
* `received → reviewing → actioned | rejected`; an actioned claim can be
|
|
43
|
+
* answered by the content's owner (`counter_noticed`), which ends either in
|
|
44
|
+
* `reinstated` (the content is served again) or `upheld` (it stays blocked).
|
|
45
|
+
* A claim can also be rejected straight from `received`.
|
|
46
|
+
*/
|
|
47
|
+
exports.CONTENT_CLAIM_STATES = [
|
|
48
|
+
'received',
|
|
49
|
+
'reviewing',
|
|
50
|
+
'actioned',
|
|
51
|
+
'rejected',
|
|
52
|
+
'counter_noticed',
|
|
53
|
+
'reinstated',
|
|
54
|
+
'upheld',
|
|
55
|
+
];
|
|
56
|
+
/** The states in which a claim is still open: its evidence is kept and its blocks can change. */
|
|
57
|
+
exports.OPEN_CONTENT_CLAIM_STATES = [
|
|
58
|
+
'received',
|
|
59
|
+
'reviewing',
|
|
60
|
+
'actioned',
|
|
61
|
+
'counter_noticed',
|
|
62
|
+
];
|
|
63
|
+
/** The states a claim ends in. */
|
|
64
|
+
exports.CLOSED_CONTENT_CLAIM_STATES = [
|
|
65
|
+
'rejected',
|
|
66
|
+
'reinstated',
|
|
67
|
+
'upheld',
|
|
68
|
+
];
|
|
69
|
+
/**
|
|
70
|
+
* The kinds whose claimant asserts a right in a work or a mark. Only these
|
|
71
|
+
* carry the link to the original — a privacy or illegal-content report
|
|
72
|
+
* asserts no ownership.
|
|
73
|
+
*/
|
|
74
|
+
function isRightsClaim(kind) {
|
|
75
|
+
return kind === 'copyright' || kind === 'trademark';
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A request to erase the claimant's OWN personal data (GDPR art. 17): a
|
|
79
|
+
* privacy claim made by the person it is about. Oxy must answer it within one
|
|
80
|
+
* month (art. 12(3)); the claim carries that deadline.
|
|
81
|
+
*/
|
|
82
|
+
function isErasureRequest(kind, relationship) {
|
|
83
|
+
return kind === 'privacy' && relationship === 'affected_person';
|
|
84
|
+
}
|
|
85
|
+
/** Days Oxy has to answer an erasure request ({@link isErasureRequest}). */
|
|
86
|
+
exports.ERASURE_REQUEST_DEADLINE_DAYS = 30;
|
|
87
|
+
/**
|
|
88
|
+
* Stable error codes a takedown produces elsewhere in the API. A file whose
|
|
89
|
+
* bytes are on the blocklist cannot be uploaded, imported or copied by anyone
|
|
90
|
+
* (HTTP 451).
|
|
91
|
+
*/
|
|
92
|
+
exports.CONTENT_CLAIM_ERROR_CODES = {
|
|
93
|
+
/** The submission failed validation; `fields` names each problem. */
|
|
94
|
+
invalid: 'CONTENT_CLAIM_INVALID',
|
|
95
|
+
/** These exact bytes were taken down after a claim and cannot be stored again. */
|
|
96
|
+
contentBlocked: 'CONTENT_BLOCKED',
|
|
97
|
+
/** A counter-notice failed validation; `fields` names each problem. */
|
|
98
|
+
counterNoticeInvalid: 'CONTENT_CLAIM_COUNTER_NOTICE_INVALID',
|
|
99
|
+
/** The files named are blocked by more than one claim: name the claim's reference. */
|
|
100
|
+
counterNoticeAmbiguous: 'CONTENT_CLAIM_COUNTER_NOTICE_AMBIGUOUS',
|
|
101
|
+
/** This account already sent a counter-notice for this claim. */
|
|
102
|
+
counterNoticeExists: 'CONTENT_CLAIM_COUNTER_NOTICE_EXISTS',
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* A claim's reference as the claimant and the owner are sent it:
|
|
106
|
+
* `OXY-XXXX-XXXX`, Crockford base32 (no I, L, O or U).
|
|
107
|
+
*/
|
|
108
|
+
exports.CONTENT_CLAIM_REFERENCE_PATTERN = /^OXY-[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}$/;
|
|
109
|
+
/** The bounds of an owner's counter-notice. The website fills its validation messages from these. */
|
|
110
|
+
exports.CONTENT_CLAIM_COUNTER_NOTICE_LIMITS = {
|
|
111
|
+
maxFileIds: 50,
|
|
112
|
+
maxFileId: 64,
|
|
113
|
+
maxName: 200,
|
|
114
|
+
maxAddress: 500,
|
|
115
|
+
minPhone: 5,
|
|
116
|
+
maxPhone: 40,
|
|
117
|
+
maxEmail: 254,
|
|
118
|
+
minMaterial: 10,
|
|
119
|
+
maxMaterial: 2000,
|
|
120
|
+
maxLocation: 2048,
|
|
121
|
+
minExplanation: 20,
|
|
122
|
+
maxExplanation: 5000,
|
|
123
|
+
maxSignature: 200,
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* The statements an owner's counter-notice makes, word for word — what the
|
|
127
|
+
* form shows beside each box (17 U.S.C. § 512(g)(3)(C) and (D), and what Oxy
|
|
128
|
+
* does with the notice). The API records {@link COUNTER_NOTICE_STATEMENT_VERSION}
|
|
129
|
+
* with every notice, so the wording a person agreed to can always be told
|
|
130
|
+
* apart from a later one. Change the text only together with the version.
|
|
131
|
+
*/
|
|
132
|
+
exports.COUNTER_NOTICE_STATEMENTS = {
|
|
133
|
+
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.',
|
|
134
|
+
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.',
|
|
135
|
+
serviceOfProcess: 'I will accept service of process from the person who provided the notification of the claimed infringement, or an agent of such person.',
|
|
136
|
+
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.',
|
|
137
|
+
};
|
|
138
|
+
/** The version of {@link COUNTER_NOTICE_STATEMENTS} in force. */
|
|
139
|
+
exports.COUNTER_NOTICE_STATEMENT_VERSION = '2026-10-16';
|