@oxy.so/contracts 4.10.0 → 4.11.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.
Files changed (100) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/accountGraph.js +5 -3
  3. package/dist/cjs/agency.js +178 -96
  4. package/dist/cjs/agentKeys.js +80 -26
  5. package/dist/cjs/capabilityBindings.js +2 -2
  6. package/dist/cjs/capabilityInvocation.js +3 -1
  7. package/dist/cjs/commonsSignIn.js +2 -2
  8. package/dist/cjs/deviceSession.js +5 -1
  9. package/dist/cjs/email/messages.js +70 -4
  10. package/dist/cjs/emailAgentContext.js +16 -8
  11. package/dist/cjs/externalIdentity.js +30 -12
  12. package/dist/cjs/identity.js +14 -6
  13. package/dist/cjs/identityLink.js +7 -2
  14. package/dist/cjs/identityProof.js +2 -1
  15. package/dist/cjs/index.js +23 -15
  16. package/dist/cjs/inference/catalogue.js +2 -12
  17. package/dist/cjs/inference/decisions.js +102 -83
  18. package/dist/cjs/inference/economics.js +15 -5
  19. package/dist/cjs/inference/embeddings.js +44 -6
  20. package/dist/cjs/inference/inbox.js +33 -2
  21. package/dist/cjs/inference/providerConnection.js +21 -6
  22. package/dist/cjs/inference/realtime.js +114 -107
  23. package/dist/cjs/inference/scopedExecution.js +12 -12
  24. package/dist/cjs/inference/usage.js +2 -9
  25. package/dist/cjs/notifications.js +25 -5
  26. package/dist/cjs/oauth.js +30 -14
  27. package/dist/cjs/oxyRecordTypes.js +2 -2
  28. package/dist/cjs/products/access.js +161 -41
  29. package/dist/cjs/products/billing.js +71 -17
  30. package/dist/cjs/products/checkout.js +35 -0
  31. package/dist/cjs/products/plans.js +38 -0
  32. package/dist/cjs/recommendations.js +4 -6
  33. package/dist/cjs/reputation.js +3 -6
  34. package/dist/cjs/sessionStatus.js +1 -6
  35. package/dist/cjs/signIn.js +17 -4
  36. package/dist/cjs/transparency.js +3 -1
  37. package/dist/cjs/updates.js +2 -4
  38. package/dist/cjs/userResponse.js +32 -9
  39. package/dist/cjs/username.js +3 -1
  40. package/dist/esm/.tsbuildinfo +1 -1
  41. package/dist/esm/accountGraph.js +5 -3
  42. package/dist/esm/agency.js +178 -96
  43. package/dist/esm/agentKeys.js +80 -26
  44. package/dist/esm/capabilityBindings.js +2 -2
  45. package/dist/esm/capabilityInvocation.js +3 -1
  46. package/dist/esm/commonsSignIn.js +2 -2
  47. package/dist/esm/deviceSession.js +5 -1
  48. package/dist/esm/email/messages.js +69 -3
  49. package/dist/esm/emailAgentContext.js +16 -8
  50. package/dist/esm/externalIdentity.js +30 -12
  51. package/dist/esm/identity.js +14 -6
  52. package/dist/esm/identityLink.js +7 -2
  53. package/dist/esm/identityProof.js +2 -1
  54. package/dist/esm/index.js +10 -8
  55. package/dist/esm/inference/aliaModelRelease.js +1 -1
  56. package/dist/esm/inference/catalogue.js +3 -13
  57. package/dist/esm/inference/decisions.js +108 -89
  58. package/dist/esm/inference/economics.js +16 -6
  59. package/dist/esm/inference/embeddings.js +44 -6
  60. package/dist/esm/inference/inbox.js +32 -1
  61. package/dist/esm/inference/providerConnection.js +21 -6
  62. package/dist/esm/inference/realtime.js +123 -116
  63. package/dist/esm/inference/scopedExecution.js +15 -15
  64. package/dist/esm/inference/usage.js +2 -9
  65. package/dist/esm/notifications.js +25 -5
  66. package/dist/esm/oauth.js +30 -14
  67. package/dist/esm/oxyRecordTypes.js +2 -2
  68. package/dist/esm/products/access.js +160 -40
  69. package/dist/esm/products/billing.js +71 -17
  70. package/dist/esm/products/checkout.js +32 -0
  71. package/dist/esm/products/plans.js +35 -0
  72. package/dist/esm/recommendations.js +4 -6
  73. package/dist/esm/reputation.js +3 -6
  74. package/dist/esm/sessionStatus.js +1 -6
  75. package/dist/esm/signIn.js +17 -4
  76. package/dist/esm/transparency.js +3 -1
  77. package/dist/esm/updates.js +2 -4
  78. package/dist/esm/userResponse.js +28 -5
  79. package/dist/esm/username.js +3 -1
  80. package/dist/types/.tsbuildinfo +1 -1
  81. package/dist/types/accountGraph.d.ts +1 -1
  82. package/dist/types/commonsSignIn.d.ts +2 -2
  83. package/dist/types/email/messages.d.ts +208 -2
  84. package/dist/types/externalIdentity.d.ts +90 -0
  85. package/dist/types/identity.d.ts +1 -1
  86. package/dist/types/index.d.ts +12 -10
  87. package/dist/types/inference/decisions.d.ts +1 -1
  88. package/dist/types/inference/inbox.d.ts +129 -5
  89. package/dist/types/inference/realtime.d.ts +1 -1
  90. package/dist/types/inference/scopedExecution.d.ts +1 -1
  91. package/dist/types/moderationReputation.d.ts +1 -1
  92. package/dist/types/oxyRecordTypes.d.ts +2 -2
  93. package/dist/types/products/access.d.ts +405 -0
  94. package/dist/types/products/billing.d.ts +3 -0
  95. package/dist/types/products/checkout.d.ts +60 -0
  96. package/dist/types/products/plans.d.ts +319 -0
  97. package/dist/types/reputation.d.ts +4 -4
  98. package/dist/types/updates.d.ts +1 -1
  99. package/dist/types/userResponse.d.ts +388 -23
  100. package/package.json +1 -2
@@ -1,8 +1,8 @@
1
- import { z } from "zod";
2
- import { deploymentIdSchema, idempotencyKeySchema, inferenceEnvironmentSchema, inferenceProviderSlugSchema, modelReferenceSchema, requestIdSchema, } from "./identifiers.js";
3
- import { exactDecimalSchema } from "./money.js";
1
+ import { z } from 'zod';
2
+ import { deploymentIdSchema, idempotencyKeySchema, inferenceEnvironmentSchema, inferenceProviderSlugSchema, modelReferenceSchema, requestIdSchema, } from './identifiers.js';
3
+ import { exactDecimalSchema } from './money.js';
4
4
  /** Explicit extension negotiation; never replaces the legacy 3.5 handshake. */
5
- export const SCOPED_EXECUTION_CONTRACT_VERSION = "3.6.0";
5
+ export const SCOPED_EXECUTION_CONTRACT_VERSION = '3.6.0';
6
6
  /** Scoped only; normal envelope v2 and metadata 3.5 remain unchanged. */
7
7
  export const SCOPED_REQUEST_ENVELOPE_VERSION = 3;
8
8
  const identity = z.string().min(1).max(256);
@@ -10,10 +10,10 @@ const sha256 = z.string().regex(/^[0-9a-f]{64}$/);
10
10
  const maxCost = exactDecimalSchema.refine((value) => {
11
11
  if (!exactDecimalSchema.safeParse(value).success)
12
12
  return false;
13
- const [whole, fraction = ""] = value.split(".");
14
- const units = BigInt(whole) * 1000000000000n + BigInt(fraction.padEnd(12, "0"));
13
+ const [whole, fraction = ''] = value.split('.');
14
+ const units = BigInt(whole) * 1000000000000n + BigInt(fraction.padEnd(12, '0'));
15
15
  return units > 0n && units <= 10000000000n;
16
- }, "scoped cost must be positive and at most 0.01 USD");
16
+ }, 'scoped cost must be positive and at most 0.01 USD');
17
17
  /** Restriction only: none of these identities grants catalogue eligibility. */
18
18
  export const scopedExecutionAudienceSchema = z
19
19
  .object({
@@ -38,7 +38,7 @@ export const scopedExecutionAudienceSchema = z
38
38
  deploymentId: deploymentIdSchema,
39
39
  provider: inferenceProviderSlugSchema,
40
40
  keyId: identity,
41
- modelReference: modelReferenceSchema.refine((value) => value.includes("@"), "must pin a revision"),
41
+ modelReference: modelReferenceSchema.refine((value) => value.includes('@'), 'must pin a revision'),
42
42
  upstreamModelId: identity,
43
43
  priceVersionId: identity,
44
44
  providerRateCardVersionId: identity,
@@ -59,25 +59,25 @@ export const scopedExecutionSchema = scopedExecutionAudienceSchema
59
59
  * reparsed/defaulted decision. Reject non-JSON values rather than dropping them.
60
60
  */
61
61
  export function canonicalScopedExecutionJson(value) {
62
- if (value === null || typeof value === "string" || typeof value === "boolean")
62
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
63
63
  return JSON.stringify(value);
64
- if (typeof value === "number" && Number.isFinite(value))
64
+ if (typeof value === 'number' && Number.isFinite(value))
65
65
  return JSON.stringify(value);
66
66
  if (Array.isArray(value)) {
67
67
  const entries = Array.from(value, (entry, index) => {
68
68
  if (!(index in value))
69
- throw new Error("Scoped execution requires dense JSON arrays.");
69
+ throw new Error('Scoped execution requires dense JSON arrays.');
70
70
  return canonicalScopedExecutionJson(entry);
71
71
  });
72
- return `[${entries.join(",")}]`;
72
+ return `[${entries.join(',')}]`;
73
73
  }
74
- if (typeof value === "object" &&
74
+ if (typeof value === 'object' &&
75
75
  value !== null &&
76
76
  Object.getPrototypeOf(value) === Object.prototype) {
77
77
  const entries = Object.keys(value)
78
78
  .sort()
79
79
  .map((key) => `${JSON.stringify(key)}:${canonicalScopedExecutionJson(value[key])}`);
80
- return `{${entries.join(",")}}`;
80
+ return `{${entries.join(',')}}`;
81
81
  }
82
- throw new Error("Scoped execution requires JSON wire values.");
82
+ throw new Error('Scoped execution requires JSON wire values.');
83
83
  }
@@ -62,12 +62,7 @@ export const usageReservationRequestSchema = z.object({
62
62
  expiresInSeconds: z.number().int().positive().max(86400),
63
63
  });
64
64
  /** Lifecycle of a hold. Terminal states are `settled`, `released`, `expired`. */
65
- export const usageReservationStatusSchema = z.enum([
66
- 'held',
67
- 'settled',
68
- 'released',
69
- 'expired',
70
- ]);
65
+ export const usageReservationStatusSchema = z.enum(['held', 'settled', 'released', 'expired']);
71
66
  /**
72
67
  * A hold placed against an account's balance.
73
68
  *
@@ -267,9 +262,7 @@ export const usageReceiptSchema = z
267
262
  * ledger accounts and only the second one is visible on an invoice.
268
263
  */
269
264
  export const usageRefundSubjectSchema = z.discriminatedUnion('kind', [
270
- z
271
- .object({ kind: z.literal('reservation'), reservationId: z.string().min(1).max(128) })
272
- .strict(),
265
+ z.object({ kind: z.literal('reservation'), reservationId: z.string().min(1).max(128) }).strict(),
273
266
  z.object({ kind: z.literal('receipt'), receiptId: z.string().min(1).max(128) }).strict(),
274
267
  ]);
275
268
  /** Why money was given back or a hold was released. */
@@ -61,18 +61,38 @@ export const createOxyNotificationRequestSchema = z
61
61
  .superRefine((value, context) => {
62
62
  if (value.type === 'system') {
63
63
  if (value.actorId !== value.recipientId)
64
- context.addIssue({ code: z.ZodIssueCode.custom, path: ['actorId'], message: 'a system notification is from the recipient\'s own account' });
64
+ context.addIssue({
65
+ code: z.ZodIssueCode.custom,
66
+ path: ['actorId'],
67
+ message: "a system notification is from the recipient's own account",
68
+ });
65
69
  if (!value.title)
66
- context.addIssue({ code: z.ZodIssueCode.custom, path: ['title'], message: 'title is required for a system notification' });
70
+ context.addIssue({
71
+ code: z.ZodIssueCode.custom,
72
+ path: ['title'],
73
+ message: 'title is required for a system notification',
74
+ });
67
75
  if (!value.message)
68
- context.addIssue({ code: z.ZodIssueCode.custom, path: ['message'], message: 'message is required for a system notification' });
76
+ context.addIssue({
77
+ code: z.ZodIssueCode.custom,
78
+ path: ['message'],
79
+ message: 'message is required for a system notification',
80
+ });
69
81
  }
70
82
  else {
71
83
  if (value.url !== undefined) {
72
- context.addIssue({ code: z.ZodIssueCode.custom, path: ['url'], message: 'url is only stored for a system notification' });
84
+ context.addIssue({
85
+ code: z.ZodIssueCode.custom,
86
+ path: ['url'],
87
+ message: 'url is only stored for a system notification',
88
+ });
73
89
  }
74
90
  if (value.entityType === 'app') {
75
- context.addIssue({ code: z.ZodIssueCode.custom, path: ['entityType'], message: "entityType 'app' is only valid for a system notification" });
91
+ context.addIssue({
92
+ code: z.ZodIssueCode.custom,
93
+ path: ['entityType'],
94
+ message: "entityType 'app' is only valid for a system notification",
95
+ });
76
96
  }
77
97
  }
78
98
  });
package/dist/esm/oauth.js CHANGED
@@ -68,45 +68,61 @@ export const oauthAuthorizeCodeResponseSchema = z.object({
68
68
  * Reuses the ordinary public application projection and adds only the public
69
69
  * DCR client id needed to bind the consent surface to the request.
70
70
  */
71
- export const mcpOAuthClientApplicationSchema = publicApplicationSchema.extend({
71
+ export const mcpOAuthClientApplicationSchema = publicApplicationSchema
72
+ .extend({
72
73
  clientId: z.string().min(1),
73
- }).strict();
74
+ })
75
+ .strict();
74
76
  /**
75
77
  * An effectful MCP action the requested capability set can actually invoke.
76
78
  * Every field is picked from the canonical catalog tool schema so consent can
77
79
  * never maintain a second tool description or effect classification.
78
80
  */
79
- export const mcpOAuthWriteActionSchema = catalogToolSchema.innerType().pick({
81
+ export const mcpOAuthWriteActionSchema = catalogToolSchema
82
+ .innerType()
83
+ .pick({
80
84
  name: true,
81
85
  version: true,
82
86
  description: true,
83
87
  requiredCapabilities: true,
84
88
  effect: true,
85
- }).strict().refine((action) => action.effect !== 'read', {
89
+ })
90
+ .strict()
91
+ .refine((action) => action.effect !== 'read', {
86
92
  message: 'MCP consent write actions must be effectful',
87
93
  path: ['effect'],
88
94
  });
89
95
  /** Exact, server-resolved context rendered before an external MCP grant. */
90
- export const mcpOAuthConsentContextSchema = z.object({
96
+ export const mcpOAuthConsentContextSchema = z
97
+ .object({
91
98
  client: mcpOAuthClientApplicationSchema,
92
- account: z.object({
99
+ account: z
100
+ .object({
93
101
  id: z.string().min(1),
94
102
  displayName: z.string().min(1).optional(),
95
103
  handle: z.string().min(1).optional(),
96
104
  avatar: z.string().min(1).optional(),
97
- }).strict(),
98
- resource: z.object({
105
+ })
106
+ .strict(),
107
+ resource: z
108
+ .object({
99
109
  appId: z.string().min(1),
100
110
  uri: z.string().url(),
101
111
  application: publicApplicationSchema,
102
- }).strict(),
112
+ })
113
+ .strict(),
103
114
  capabilities: z.array(z.string().min(1)).min(1),
104
115
  writeActions: z.array(mcpOAuthWriteActionSchema),
105
- }).strict();
106
- export const mcpOAuthClientInfoResponseSchema = z.object({
116
+ })
117
+ .strict();
118
+ export const mcpOAuthClientInfoResponseSchema = z
119
+ .object({
107
120
  application: mcpOAuthClientApplicationSchema,
108
- }).strict();
109
- export const mcpOAuthConsentResponseSchema = z.object({
121
+ })
122
+ .strict();
123
+ export const mcpOAuthConsentResponseSchema = z
124
+ .object({
110
125
  consentRequired: z.boolean(),
111
126
  context: mcpOAuthConsentContextSchema,
112
- }).strict();
127
+ })
128
+ .strict();
@@ -7,8 +7,8 @@
7
7
  * is rejected as `invalid_envelope`.
8
8
  *
9
9
  * `oxySignedRecordTypeSchema` is that runtime gate (the API's `verifyEnvelope`
10
- * re-narrows with it; the Mongoose `SignedRecord.type` enum and the Postgres
11
- * CHECK on `signed_records.type` are both derived from `.options`);
10
+ * re-narrows with it; the Postgres CHECK on `signed_records.type` is derived
11
+ * from `.options`);
12
12
  * `OxySignedRecordType` is the matching compile-time union the SDK
13
13
  * identity/civic mixins type against.
14
14
  *
@@ -2,56 +2,176 @@ import { z } from 'zod';
2
2
  import { oxyAccountIdSchema, oxyApplicationIdSchema } from '../inference/identifiers.js';
3
3
  const identifier = z.string().min(1).max(160);
4
4
  const count = z.number().int().nonnegative().safe();
5
- const period = z.object({ start: z.string().datetime(), end: z.string().datetime() }).strict()
6
- .refine(value => Date.parse(value.end) > Date.parse(value.start), 'period end must follow start');
5
+ const period = z
6
+ .object({ start: z.string().datetime(), end: z.string().datetime() })
7
+ .strict()
8
+ .refine((value) => Date.parse(value.end) > Date.parse(value.start), 'period end must follow start');
7
9
  /** Product ownership and audience are registered together; branding grants no trust. */
8
- export const productDefinitionSchema = z.object({
9
- schemaVersion: z.literal(1), id: identifier, ownerAccountId: oxyAccountIdSchema,
10
+ export const productDefinitionSchema = z
11
+ .object({
12
+ schemaVersion: z.literal(1),
13
+ id: identifier,
14
+ ownerAccountId: oxyAccountIdSchema,
10
15
  applicationId: oxyApplicationIdSchema,
11
- }).strict();
12
- export const subjectProductAccessQuerySchema = z.object({
13
- schemaVersion: z.literal(1), subjectAccountId: oxyAccountIdSchema, productId: identifier,
14
- }).strict();
16
+ })
17
+ .strict();
18
+ export const subjectProductAccessQuerySchema = z
19
+ .object({
20
+ schemaVersion: z.literal(1),
21
+ subjectAccountId: oxyAccountIdSchema,
22
+ productId: identifier,
23
+ })
24
+ .strict();
15
25
  /** Every quota declares its combination rule; there is no commercial default. */
16
26
  export const productBenefitSchema = z.discriminatedUnion('kind', [
17
27
  z.object({ kind: z.literal('capability'), productId: identifier, key: identifier }).strict(),
18
- z.object({ kind: z.literal('quota'), productId: identifier, key: identifier,
19
- unit: identifier, included: count, combination: z.enum(['maximum', 'sum', 'exclusive']) }).strict(),
28
+ z
29
+ .object({
30
+ kind: z.literal('quota'),
31
+ productId: identifier,
32
+ key: identifier,
33
+ unit: identifier,
34
+ included: count,
35
+ combination: z.enum(['maximum', 'sum', 'exclusive']),
36
+ })
37
+ .strict(),
20
38
  ]);
21
39
  /** A version names a frozen offer. An empty bundle grants nothing. */
22
- export const productOfferSchema = z.object({
23
- schemaVersion: z.literal(1), id: identifier, version: z.number().int().positive().safe(),
24
- kind: z.enum(['individual', 'bundle']), benefits: z.array(productBenefitSchema),
25
- }).strict().refine(value => value.kind === 'bundle' || new Set(value.benefits.map(benefit => benefit.productId)).size <= 1, 'individual offers cannot cover multiple products');
26
- export const productSubscriptionSourceSchema = z.object({
27
- schemaVersion: z.literal(1), id: identifier,
28
- beneficiaryAccountId: oxyAccountIdSchema, payerAccountId: oxyAccountIdSchema,
29
- provider: z.enum(['stripe', 'peable']), providerSubscriptionId: identifier,
30
- status: z.enum(['active', 'trialing', 'past_due', 'unpaid', 'canceled', 'paused', 'incomplete', 'incomplete_expired']),
31
- period, cancelAtPeriodEnd: z.boolean(),
32
- }).strict();
40
+ export const productOfferSchema = z
41
+ .object({
42
+ schemaVersion: z.literal(1),
43
+ id: identifier,
44
+ version: z.number().int().positive().safe(),
45
+ kind: z.enum(['individual', 'bundle']),
46
+ benefits: z.array(productBenefitSchema),
47
+ })
48
+ .strict()
49
+ .refine((value) => value.kind === 'bundle' ||
50
+ new Set(value.benefits.map((benefit) => benefit.productId)).size <= 1, 'individual offers cannot cover multiple products');
51
+ export const productSubscriptionSourceSchema = z
52
+ .object({
53
+ schemaVersion: z.literal(1),
54
+ id: identifier,
55
+ beneficiaryAccountId: oxyAccountIdSchema,
56
+ payerAccountId: oxyAccountIdSchema,
57
+ provider: z.enum(['stripe', 'peable']),
58
+ providerSubscriptionId: identifier,
59
+ status: z.enum([
60
+ 'active',
61
+ 'trialing',
62
+ 'past_due',
63
+ 'unpaid',
64
+ 'canceled',
65
+ 'paused',
66
+ 'incomplete',
67
+ 'incomplete_expired',
68
+ ]),
69
+ period,
70
+ cancelAtPeriodEnd: z.boolean(),
71
+ })
72
+ .strict();
33
73
  /** A frozen paid-period/offer segment; upgrades append, never rewrite it. */
34
- export const productOfferSegmentSchema = z.object({
35
- schemaVersion: z.literal(1), id: identifier, subscriptionId: identifier,
36
- beneficiaryAccountId: oxyAccountIdSchema, offerId: identifier,
37
- offerVersion: z.number().int().positive().safe(), origin: z.enum(['individual', 'bundle']), period,
38
- }).strict();
74
+ export const productOfferSegmentSchema = z
75
+ .object({
76
+ schemaVersion: z.literal(1),
77
+ id: identifier,
78
+ subscriptionId: identifier,
79
+ beneficiaryAccountId: oxyAccountIdSchema,
80
+ offerId: identifier,
81
+ offerVersion: z.number().int().positive().safe(),
82
+ origin: z.enum(['individual', 'bundle']),
83
+ period,
84
+ })
85
+ .strict();
39
86
  /** Provenance survives cancellation: deactivate a source, never delete history. */
40
- export const productAccessGrantSchema = z.object({
41
- schemaVersion: z.literal(1), id: identifier, sourceSegmentId: identifier,
42
- beneficiaryAccountId: oxyAccountIdSchema, offerId: identifier,
87
+ export const productAccessGrantSchema = z
88
+ .object({
89
+ schemaVersion: z.literal(1),
90
+ id: identifier,
91
+ sourceSegmentId: identifier,
92
+ beneficiaryAccountId: oxyAccountIdSchema,
93
+ offerId: identifier,
43
94
  offerVersion: z.number().int().positive().safe(),
44
- origin: z.enum(['individual', 'bundle']), benefit: productBenefitSchema,
45
- period, revokedAt: z.string().datetime().nullable(),
46
- }).strict();
95
+ origin: z.enum(['individual', 'bundle']),
96
+ benefit: productBenefitSchema,
97
+ period,
98
+ revokedAt: z.string().datetime().nullable(),
99
+ })
100
+ .strict();
47
101
  /** Access answers contain no payer, provider reference, price or account balance. */
48
- export const subjectProductAccessSchema = z.object({
49
- schemaVersion: z.literal(1), subjectAccountId: oxyAccountIdSchema, productId: identifier,
102
+ export const subjectProductAccessSchema = z
103
+ .object({
104
+ schemaVersion: z.literal(1),
105
+ subjectAccountId: oxyAccountIdSchema,
106
+ productId: identifier,
50
107
  evaluatedAt: z.string().datetime(),
51
108
  capabilities: z.array(z.object({ key: identifier, grantIds: z.array(identifier) }).strict()),
52
- quotas: z.array(z.object({ key: identifier, unit: identifier, included: count,
53
- combination: z.enum(['maximum', 'sum', 'exclusive']), grantIds: z.array(identifier) }).strict()),
54
- conflicts: z.array(z.object({ key: identifier,
55
- reason: z.enum(['combination_mismatch', 'unit_mismatch', 'exclusive_overlap', 'unsafe_total']),
56
- grantIds: z.array(identifier) }).strict()),
57
- }).strict();
109
+ quotas: z.array(z
110
+ .object({
111
+ key: identifier,
112
+ unit: identifier,
113
+ included: count,
114
+ combination: z.enum(['maximum', 'sum', 'exclusive']),
115
+ grantIds: z.array(identifier),
116
+ })
117
+ .strict()),
118
+ conflicts: z.array(z
119
+ .object({
120
+ key: identifier,
121
+ reason: z.enum([
122
+ 'combination_mismatch',
123
+ 'unit_mismatch',
124
+ 'exclusive_overlap',
125
+ 'unsafe_total',
126
+ ]),
127
+ grantIds: z.array(identifier),
128
+ })
129
+ .strict()),
130
+ })
131
+ .strict();
132
+ /** Active grant periods for metering adapters; no financial/provider identity. */
133
+ export const subjectProductGrantSnapshotSchema = z
134
+ .object({
135
+ schemaVersion: z.literal(1),
136
+ access: subjectProductAccessSchema,
137
+ grants: z.array(productAccessGrantSchema),
138
+ })
139
+ .strict()
140
+ .superRefine((value, ctx) => {
141
+ const active = new Set([...value.access.capabilities, ...value.access.quotas].flatMap((entry) => entry.grantIds));
142
+ if (active.size !== value.grants.length ||
143
+ new Set(value.grants.map((g) => g.id)).size !== value.grants.length ||
144
+ value.grants.some((g) => g.beneficiaryAccountId !== value.access.subjectAccountId ||
145
+ g.benefit.productId !== value.access.productId ||
146
+ !active.has(g.id) ||
147
+ // A future revocation is still active at evaluation time, as in access composition.
148
+ (g.revokedAt !== null &&
149
+ Date.parse(g.revokedAt) <= Date.parse(value.access.evaluatedAt)) ||
150
+ Date.parse(g.period.start) > Date.parse(value.access.evaluatedAt) ||
151
+ Date.parse(g.period.end) <= Date.parse(value.access.evaluatedAt)))
152
+ ctx.addIssue({ code: 'custom', message: 'Active grant snapshot attribution differs' });
153
+ for (const quota of value.access.quotas) {
154
+ const benefits = quota.grantIds.map((id) => value.grants.find((g) => g.id === id)?.benefit);
155
+ if (benefits.some((b) => !b ||
156
+ b.kind !== 'quota' ||
157
+ b.key !== quota.key ||
158
+ b.unit !== quota.unit ||
159
+ b.combination !== quota.combination)) {
160
+ ctx.addIssue({ code: 'custom', message: 'Grant quota terms differ' });
161
+ continue;
162
+ }
163
+ const amounts = benefits.map((b) => (b?.kind === 'quota' ? BigInt(b.included) : 0n));
164
+ const included = quota.combination === 'sum'
165
+ ? amounts.reduce((sum, n) => sum + n, 0n)
166
+ : amounts.reduce((maximum, n) => (n > maximum ? n : maximum), 0n);
167
+ if (included !== BigInt(quota.included) ||
168
+ (quota.combination === 'exclusive' && benefits.length > 1))
169
+ ctx.addIssue({ code: 'custom', message: 'Grant quota composition differs' });
170
+ }
171
+ for (const capability of value.access.capabilities)
172
+ if (capability.grantIds.some((id) => {
173
+ const benefit = value.grants.find((g) => g.id === id)?.benefit;
174
+ return benefit?.kind !== 'capability' || benefit.key !== capability.key;
175
+ }))
176
+ ctx.addIssue({ code: 'custom', message: 'Grant capability terms differ' });
177
+ });
@@ -3,25 +3,79 @@ import { oxyAccountIdSchema } from '../inference/identifiers.js';
3
3
  const id = z.string().min(1).max(160);
4
4
  const name = z.string().min(1).max(100);
5
5
  const count = z.number().int().nonnegative().safe();
6
- const period = z.object({ start: z.string().datetime(), end: z.string().datetime() }).strict()
7
- .refine(value => Date.parse(value.end) > Date.parse(value.start), 'Period end must follow start');
6
+ const period = z
7
+ .object({ start: z.string().datetime(), end: z.string().datetime() })
8
+ .strict()
9
+ .refine((value) => Date.parse(value.end) > Date.parse(value.start), 'Period end must follow start');
8
10
  /** Self-service provenance; no payer/provider identifiers or balances in access metadata. */
9
- export const productSubscriptionSummarySchema = z.object({ sourceId: id,
10
- status: z.enum(['active', 'trialing', 'past_due', 'unpaid', 'canceled', 'paused', 'incomplete', 'incomplete_expired']),
11
- period, cancelAtPeriodEnd: z.boolean(), canCancel: z.boolean(),
12
- offers: z.array(z.object({ segmentId: id, offerId: id, offerVersion: z.number().int().positive().safe(),
13
- displayName: name, origin: z.enum(['individual', 'bundle']), period, current: z.boolean(),
11
+ export const productSubscriptionSummarySchema = z
12
+ .object({
13
+ sourceId: id,
14
+ status: z.enum([
15
+ 'active',
16
+ 'trialing',
17
+ 'past_due',
18
+ 'unpaid',
19
+ 'canceled',
20
+ 'paused',
21
+ 'incomplete',
22
+ 'incomplete_expired',
23
+ ]),
24
+ period,
25
+ cancelAtPeriodEnd: z.boolean(),
26
+ canCancel: z.boolean(),
27
+ offers: z.array(z
28
+ .object({
29
+ segmentId: id,
30
+ offerId: id,
31
+ offerVersion: z.number().int().positive().safe(),
32
+ displayName: name,
33
+ origin: z.enum(['individual', 'bundle']),
34
+ period,
35
+ current: z.boolean(),
14
36
  products: z.array(z.object({ id, displayName: name }).strict()),
15
- }).strict()),
16
- }).strict();
17
- export const productSubscriptionsResponseSchema = z.object({ subscriptions: z.array(productSubscriptionSummarySchema) }).strict();
18
- export const subscriptionCreditGrantSchema = z.object({ id, invoiceId: id,
19
- origin: z.enum(['subscription_payment', 'subscription_proration', 'subscription_promotional_grant']),
20
- period, granted: count, consumed: count, clawed: count, remaining: count,
21
- promotionId: id.nullable(), createdAt: z.string().datetime(),
22
- }).strict().refine(value => BigInt(value.consumed) + BigInt(value.clawed) + BigInt(value.remaining) === BigInt(value.granted), 'Grant conservation differs');
23
- export const subscriptionCreditGrantsResponseSchema = z.object({ grants: z.array(subscriptionCreditGrantSchema) }).strict();
24
- export const cancelProductSubscriptionSchema = z.object({ sourceId: id, expectedSubjectAccountId: oxyAccountIdSchema.optional() }).strict();
37
+ })
38
+ .strict()),
39
+ })
40
+ .strict();
41
+ export const productSubscriptionsResponseSchema = z
42
+ .object({ subscriptions: z.array(productSubscriptionSummarySchema) })
43
+ .strict();
44
+ export const subscriptionCreditGrantSchema = z
45
+ .object({
46
+ id,
47
+ invoiceId: id,
48
+ origin: z.enum([
49
+ 'subscription_payment',
50
+ 'subscription_proration',
51
+ 'subscription_promotional_grant',
52
+ ]),
53
+ period,
54
+ granted: count,
55
+ consumed: count,
56
+ clawed: count,
57
+ remaining: count,
58
+ promotionId: id.nullable(),
59
+ createdAt: z.string().datetime(),
60
+ })
61
+ .strict()
62
+ .refine((value) => BigInt(value.consumed) + BigInt(value.clawed) + BigInt(value.remaining) ===
63
+ BigInt(value.granted), 'Grant conservation differs');
64
+ export const subscriptionCreditGrantsResponseSchema = z
65
+ .object({ grants: z.array(subscriptionCreditGrantSchema) })
66
+ .strict();
67
+ export const cancelProductSubscriptionSchema = z
68
+ .object({
69
+ sourceId: id,
70
+ actionId: z
71
+ .string()
72
+ .min(8)
73
+ .max(160)
74
+ .regex(/^[a-zA-Z0-9_-]+$/)
75
+ .optional(),
76
+ expectedSubjectAccountId: oxyAccountIdSchema.optional(),
77
+ })
78
+ .strict();
25
79
  /** Remote acceptance is distinct from completed local reconciliation. */
26
80
  export const productSubscriptionCancellationResultSchema = z.union([
27
81
  z.object({ sourceId: id, cancelAtPeriodEnd: z.literal(true) }).strict(),
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod';
2
+ import { oxyAccountIdSchema } from '../inference/identifiers.js';
3
+ export const personalPlanCheckoutRequestSchema = z
4
+ .object({
5
+ expectedSubjectAccountId: oxyAccountIdSchema,
6
+ offerId: z.string().min(1).max(160),
7
+ offerVersion: z.number().int().positive().safe(),
8
+ idempotencyKey: z
9
+ .string()
10
+ .min(8)
11
+ .max(160)
12
+ .regex(/^[a-zA-Z0-9_-]+$/),
13
+ })
14
+ .strict();
15
+ /** No grant or subscription is created by checkout completion in the browser. */
16
+ export const personalPlanCheckoutResultSchema = z.discriminatedUnion('state', [
17
+ z
18
+ .object({
19
+ state: z.literal('unconfigured'),
20
+ reason: z.enum(['provider_unconfigured', 'offer_unconfigured', 'price_unconfigured']),
21
+ })
22
+ .strict(),
23
+ z
24
+ .object({
25
+ state: z.literal('pending'),
26
+ intentId: z.string().min(1).max(160),
27
+ checkoutUrl: z.string().url().startsWith('https://').optional(),
28
+ })
29
+ .strict(),
30
+ z.object({ state: z.literal('closed'), intentId: z.string().min(1).max(160) }).strict(),
31
+ z.object({ state: z.literal('fulfilled'), intentId: z.string().min(1).max(160) }).strict(),
32
+ ]);
@@ -0,0 +1,35 @@
1
+ import { z } from 'zod';
2
+ import { productBenefitSchema } from './access.js';
3
+ /** Approved display terms never identify or activate a billing-provider price. */
4
+ export const personalPlanDisplayPriceSchema = z
5
+ .object({
6
+ currency: z.string().regex(/^[A-Z]{3}$/),
7
+ amountMinorUnits: z.number().int().positive().safe(),
8
+ interval: z.literal('month'),
9
+ trial: z.literal('none'),
10
+ taxTreatment: z.literal('inclusive'),
11
+ merchantTotal: z.literal('final'),
12
+ })
13
+ .strict();
14
+ /** Public discovery is separate from customer access. No provider or owner IDs. */
15
+ export const personalPlanCatalogueSchema = z
16
+ .object({
17
+ schemaVersion: z.literal(1),
18
+ state: z.enum(['unconfigured', 'configured']),
19
+ purchase: z.literal('unavailable'),
20
+ plans: z.array(z
21
+ .object({
22
+ offerId: z.string().min(1).max(160),
23
+ offerVersion: z.number().int().positive().safe(),
24
+ displayName: z.string().min(1).max(100),
25
+ audience: z.literal('personal'),
26
+ kind: z.literal('oxy_one'),
27
+ price: personalPlanDisplayPriceSchema.optional(),
28
+ benefits: z.array(z
29
+ .object({ displayName: z.string().min(1).max(100), benefit: productBenefitSchema })
30
+ .strict()),
31
+ })
32
+ .strict()),
33
+ })
34
+ .strict()
35
+ .refine((value) => (value.state === 'unconfigured') === (value.plans.length === 0), 'Catalogue state must match published plans');
@@ -13,11 +13,7 @@
13
13
  import { z } from 'zod';
14
14
  import { userNameSchema } from './userResponse.js';
15
15
  /** User-type filters a caller may exclude from the recommendation surface. */
16
- export const recommendationExcludeTypeSchema = z.enum([
17
- 'federated',
18
- 'agent',
19
- 'automated',
20
- ]);
16
+ export const recommendationExcludeTypeSchema = z.enum(['federated', 'agent', 'automated']);
21
17
  /**
22
18
  * A caller-supplied editorial boost. `userIds` are nudged up (or down, for a
23
19
  * negative weight) in the ranking; the optional `reason` is for audit/telemetry
@@ -122,7 +118,9 @@ export const appUserSignalIngestSchema = z
122
118
  endorsements: z.array(appEndorsementInputSchema).max(500).optional(),
123
119
  interests: z.array(appInterestInputSchema).max(500).optional(),
124
120
  })
125
- .refine((value) => (value.endorsements?.length ?? 0) > 0 || (value.interests?.length ?? 0) > 0, { message: 'At least one of endorsements or interests must be non-empty' });
121
+ .refine((value) => (value.endorsements?.length ?? 0) > 0 || (value.interests?.length ?? 0) > 0, {
122
+ message: 'At least one of endorsements or interests must be non-empty',
123
+ });
126
124
  /**
127
125
  * The directed interaction types a consuming app may report between two users.
128
126
  * Each type carries a server-side default weight (see the API's