@oxy.so/contracts 1.0.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 (147) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +16 -0
  3. package/dist/cjs/.tsbuildinfo +1 -0
  4. package/dist/cjs/accountGraph.js +489 -0
  5. package/dist/cjs/agency.js +439 -0
  6. package/dist/cjs/browserHub.js +215 -0
  7. package/dist/cjs/civic.js +163 -0
  8. package/dist/cjs/commonsSignIn.js +59 -0
  9. package/dist/cjs/deviceBoot.js +50 -0
  10. package/dist/cjs/deviceDirectory.js +189 -0
  11. package/dist/cjs/devicePairing.js +138 -0
  12. package/dist/cjs/deviceSession.js +164 -0
  13. package/dist/cjs/emailAgentContext.js +32 -0
  14. package/dist/cjs/followGraph.js +28 -0
  15. package/dist/cjs/identity.js +258 -0
  16. package/dist/cjs/inboxPush.js +24 -0
  17. package/dist/cjs/index.js +618 -0
  18. package/dist/cjs/inference/accountBilling.js +334 -0
  19. package/dist/cjs/inference/aliaModelRelease.js +262 -0
  20. package/dist/cjs/inference/attribution.js +106 -0
  21. package/dist/cjs/inference/catalogue.js +487 -0
  22. package/dist/cjs/inference/entitlement.js +217 -0
  23. package/dist/cjs/inference/errors.js +309 -0
  24. package/dist/cjs/inference/identifiers.js +224 -0
  25. package/dist/cjs/inference/inbox.js +105 -0
  26. package/dist/cjs/inference/modelDocumentation.js +433 -0
  27. package/dist/cjs/inference/money.js +188 -0
  28. package/dist/cjs/inference/priceVersion.js +110 -0
  29. package/dist/cjs/inference/providerConnection.js +455 -0
  30. package/dist/cjs/inference/request.js +477 -0
  31. package/dist/cjs/inference/routingPolicy.js +318 -0
  32. package/dist/cjs/inference/streamEvents.js +258 -0
  33. package/dist/cjs/inference/usage.js +329 -0
  34. package/dist/cjs/inference/version.js +105 -0
  35. package/dist/cjs/keyRecovery.js +91 -0
  36. package/dist/cjs/keyRotation.js +75 -0
  37. package/dist/cjs/links.js +68 -0
  38. package/dist/cjs/moderationReputation.js +298 -0
  39. package/dist/cjs/oauth.js +66 -0
  40. package/dist/cjs/oxyRecordTypes.js +71 -0
  41. package/dist/cjs/protocol.js +53 -0
  42. package/dist/cjs/recommendations.js +168 -0
  43. package/dist/cjs/reputation.js +297 -0
  44. package/dist/cjs/sessionStatus.js +121 -0
  45. package/dist/cjs/transparency.js +89 -0
  46. package/dist/cjs/updates.js +252 -0
  47. package/dist/cjs/userInvalidation.js +89 -0
  48. package/dist/cjs/userResponse.js +245 -0
  49. package/dist/cjs/username.js +290 -0
  50. package/dist/cjs/webauthn.js +71 -0
  51. package/dist/esm/.tsbuildinfo +1 -0
  52. package/dist/esm/accountGraph.js +480 -0
  53. package/dist/esm/agency.js +436 -0
  54. package/dist/esm/browserHub.js +212 -0
  55. package/dist/esm/civic.js +160 -0
  56. package/dist/esm/commonsSignIn.js +56 -0
  57. package/dist/esm/deviceBoot.js +47 -0
  58. package/dist/esm/deviceDirectory.js +186 -0
  59. package/dist/esm/devicePairing.js +135 -0
  60. package/dist/esm/deviceSession.js +161 -0
  61. package/dist/esm/emailAgentContext.js +29 -0
  62. package/dist/esm/followGraph.js +27 -0
  63. package/dist/esm/identity.js +255 -0
  64. package/dist/esm/inboxPush.js +21 -0
  65. package/dist/esm/index.js +172 -0
  66. package/dist/esm/inference/accountBilling.js +331 -0
  67. package/dist/esm/inference/aliaModelRelease.js +259 -0
  68. package/dist/esm/inference/attribution.js +103 -0
  69. package/dist/esm/inference/catalogue.js +484 -0
  70. package/dist/esm/inference/entitlement.js +214 -0
  71. package/dist/esm/inference/errors.js +306 -0
  72. package/dist/esm/inference/identifiers.js +221 -0
  73. package/dist/esm/inference/inbox.js +102 -0
  74. package/dist/esm/inference/modelDocumentation.js +430 -0
  75. package/dist/esm/inference/money.js +185 -0
  76. package/dist/esm/inference/priceVersion.js +107 -0
  77. package/dist/esm/inference/providerConnection.js +452 -0
  78. package/dist/esm/inference/request.js +474 -0
  79. package/dist/esm/inference/routingPolicy.js +315 -0
  80. package/dist/esm/inference/streamEvents.js +255 -0
  81. package/dist/esm/inference/usage.js +326 -0
  82. package/dist/esm/inference/version.js +102 -0
  83. package/dist/esm/keyRecovery.js +88 -0
  84. package/dist/esm/keyRotation.js +72 -0
  85. package/dist/esm/links.js +65 -0
  86. package/dist/esm/moderationReputation.js +295 -0
  87. package/dist/esm/oauth.js +63 -0
  88. package/dist/esm/oxyRecordTypes.js +68 -0
  89. package/dist/esm/protocol.js +50 -0
  90. package/dist/esm/recommendations.js +165 -0
  91. package/dist/esm/reputation.js +293 -0
  92. package/dist/esm/sessionStatus.js +118 -0
  93. package/dist/esm/transparency.js +86 -0
  94. package/dist/esm/updates.js +249 -0
  95. package/dist/esm/userInvalidation.js +85 -0
  96. package/dist/esm/userResponse.js +240 -0
  97. package/dist/esm/username.js +283 -0
  98. package/dist/esm/webauthn.js +68 -0
  99. package/dist/types/.tsbuildinfo +1 -0
  100. package/dist/types/accountGraph.d.ts +378 -0
  101. package/dist/types/agency.d.ts +2162 -0
  102. package/dist/types/browserHub.d.ts +856 -0
  103. package/dist/types/civic.d.ts +338 -0
  104. package/dist/types/commonsSignIn.d.ts +58 -0
  105. package/dist/types/deviceBoot.d.ts +74 -0
  106. package/dist/types/deviceDirectory.d.ts +1317 -0
  107. package/dist/types/devicePairing.d.ts +130 -0
  108. package/dist/types/deviceSession.d.ts +411 -0
  109. package/dist/types/emailAgentContext.d.ts +248 -0
  110. package/dist/types/followGraph.d.ts +150 -0
  111. package/dist/types/identity.d.ts +402 -0
  112. package/dist/types/inboxPush.d.ts +30 -0
  113. package/dist/types/index.d.ts +100 -0
  114. package/dist/types/inference/accountBilling.d.ts +738 -0
  115. package/dist/types/inference/aliaModelRelease.d.ts +609 -0
  116. package/dist/types/inference/attribution.d.ts +176 -0
  117. package/dist/types/inference/catalogue.d.ts +1618 -0
  118. package/dist/types/inference/entitlement.d.ts +519 -0
  119. package/dist/types/inference/errors.d.ts +242 -0
  120. package/dist/types/inference/identifiers.d.ts +182 -0
  121. package/dist/types/inference/inbox.d.ts +374 -0
  122. package/dist/types/inference/modelDocumentation.d.ts +1603 -0
  123. package/dist/types/inference/money.d.ts +185 -0
  124. package/dist/types/inference/priceVersion.d.ts +182 -0
  125. package/dist/types/inference/providerConnection.d.ts +968 -0
  126. package/dist/types/inference/request.d.ts +2800 -0
  127. package/dist/types/inference/routingPolicy.d.ts +616 -0
  128. package/dist/types/inference/streamEvents.d.ts +950 -0
  129. package/dist/types/inference/usage.d.ts +1164 -0
  130. package/dist/types/inference/version.d.ts +102 -0
  131. package/dist/types/keyRecovery.d.ts +138 -0
  132. package/dist/types/keyRotation.d.ts +103 -0
  133. package/dist/types/links.d.ts +96 -0
  134. package/dist/types/moderationReputation.d.ts +487 -0
  135. package/dist/types/oauth.d.ts +86 -0
  136. package/dist/types/oxyRecordTypes.d.ts +62 -0
  137. package/dist/types/protocol.d.ts +86 -0
  138. package/dist/types/recommendations.d.ts +542 -0
  139. package/dist/types/reputation.d.ts +457 -0
  140. package/dist/types/sessionStatus.d.ts +231 -0
  141. package/dist/types/transparency.d.ts +392 -0
  142. package/dist/types/updates.d.ts +545 -0
  143. package/dist/types/userInvalidation.d.ts +94 -0
  144. package/dist/types/userResponse.d.ts +1706 -0
  145. package/dist/types/username.d.ts +265 -0
  146. package/dist/types/webauthn.d.ts +77 -0
  147. package/package.json +87 -0
@@ -0,0 +1,161 @@
1
+ import { z } from 'zod';
2
+ export const sessionAccountSchema = z.object({
3
+ accountId: z.string(),
4
+ sessionId: z.string(),
5
+ authuser: z.number().int().nonnegative(),
6
+ operatedByUserId: z.string().optional(),
7
+ });
8
+ export const deviceSessionStateSchema = z.object({
9
+ deviceId: z.string(),
10
+ accounts: z.array(sessionAccountSchema),
11
+ activeAccountId: z.string().nullable(),
12
+ revision: z.number().int().nonnegative(),
13
+ updatedAt: z.number(),
14
+ });
15
+ export const activeTokenSchema = z.object({
16
+ accessToken: z.string(),
17
+ expiresAt: z.string(),
18
+ });
19
+ export const deviceSessionSyncSchema = z.object({
20
+ state: deviceSessionStateSchema,
21
+ activeToken: activeTokenSchema.nullable(),
22
+ });
23
+ /* -------------------------------------------------------------------------- */
24
+ /* Device-secret token mint (phase 2c — zero-cookie transport) */
25
+ /* -------------------------------------------------------------------------- */
26
+ /**
27
+ * Request body for `POST /session/device/token` — the client presents the
28
+ * `deviceId` it stored first-party plus the opaque `deviceSecret`. NO bearer:
29
+ * possession of the secret IS the proof of device ownership. The server matches
30
+ * `sha256(deviceSecret)` against the device's stored `secretHash` (constant-time)
31
+ * and mints a short access token for the device's active account.
32
+ *
33
+ * `accountId` pins the mint to ONE account of that device instead of whichever
34
+ * account is currently active. It exists for identity-bound clients (Commons),
35
+ * whose authenticated user is determined by a local cryptographic key and must
36
+ * never follow an account switch made by another app on the same device. The
37
+ * account must already be a member of the device session; the mint NEVER
38
+ * mutates `activeAccountId`, so pinning is read-only with respect to the device
39
+ * state every other app observes.
40
+ */
41
+ export const deviceTokenMintRequestSchema = z.object({
42
+ deviceId: z.string().min(1),
43
+ deviceSecret: z.string().min(1),
44
+ accountId: z.string().min(1).optional(),
45
+ });
46
+ /**
47
+ * Wire shape of a successful `POST /session/device/token`: the freshly-minted
48
+ * short access token for the active account, its expiry, the device secret the
49
+ * client must persist (`nextDeviceSecret` — on mint this echoes the presented
50
+ * secret unchanged so concurrent refreshes from multiple origins do not race),
51
+ * and the projected device-session state. Sign-in rotates the secret via
52
+ * `issueDeviceSecret`; mint does not.
53
+ */
54
+ export const deviceTokenMintResponseSchema = z.object({
55
+ accessToken: z.string(),
56
+ expiresAt: z.string(),
57
+ nextDeviceSecret: z.string(),
58
+ state: deviceSessionStateSchema,
59
+ });
60
+ /* -------------------------------------------------------------------------- */
61
+ /* Instant cross-app session sync (token-free socket signal) */
62
+ /* -------------------------------------------------------------------------- */
63
+ /**
64
+ * Name of the token-free Socket.IO event emitted to room `user:<userId>` on
65
+ * every DeviceSession mutation that changes what is signed in for that user.
66
+ *
67
+ * This is a pure SIGNAL — it carries NO access token, NO deviceSecret and NO
68
+ * account bodies. A client that receives it re-fetches its authenticated
69
+ * session/account state (`GET /session/device/state`, `GET /accounts`). Unlike
70
+ * `session_state` (scoped to `device:<deviceId>`, i.e. a single origin), this
71
+ * reaches ALL of a user's connected sockets across their devices/origins so
72
+ * every Oxy app reflects an add / switch / signout instantly.
73
+ */
74
+ export const SESSION_ACCOUNTS_CHANGED_EVENT = 'session_accounts_changed';
75
+ /**
76
+ * Why the signed-in set changed:
77
+ * - `login` — a brand-new session was minted for the user (QR / cross-app authorize)
78
+ * - `add` — an account was registered onto a device set
79
+ * - `switch` — the active account on a device changed
80
+ * - `signout` — one or all accounts were signed out of a device
81
+ * - `revoke` — a dead/revoked account was healed out of a device set
82
+ */
83
+ export const sessionAccountsChangedReasonSchema = z.enum([
84
+ 'login',
85
+ 'add',
86
+ 'switch',
87
+ 'signout',
88
+ 'revoke',
89
+ ]);
90
+ /**
91
+ * Payload of {@link SESSION_ACCOUNTS_CHANGED_EVENT}. `revision` is the mutated
92
+ * DeviceSession revision for device-scoped reasons (`add`/`switch`/`signout`/
93
+ * `revoke`); for `login` (no device mutation at emit time) it is `0`. The
94
+ * payload is deliberately minimal and secret-free — the client refetches.
95
+ */
96
+ export const sessionAccountsChangedEventSchema = z.object({
97
+ userId: z.string(),
98
+ revision: z.number().int().nonnegative(),
99
+ reason: sessionAccountsChangedReasonSchema,
100
+ });
101
+ /* -------------------------------------------------------------------------- */
102
+ /* Background credential — native background code with no JS runtime */
103
+ /* -------------------------------------------------------------------------- */
104
+ /**
105
+ * Response from `POST /session/device/background-credential` — provisioned by
106
+ * the SDK WHILE THE APP IS RUNNING (bearer required, `deviceId` and account
107
+ * derived server-side from it) and consumed afterwards only by native
108
+ * background code, which has no JS runtime to mint a token for itself.
109
+ *
110
+ * Deliberately a SEPARATE credential from the rotating `deviceSecret`: that one
111
+ * rotates on every mint, so background code presenting it would become a second
112
+ * writer of a value the JS runtime depends on, and background code killed
113
+ * mid-rotation would silently sign the user out on the next cold start. Against
114
+ * this credential background code is the sole writer, and it can never rotate
115
+ * anything JS reads.
116
+ *
117
+ * The raw `secret` is returned exactly once, at provision time — never stored
118
+ * retrievably, never logged, never re-read. A caller that loses it provisions
119
+ * a new one.
120
+ *
121
+ * `expiresAt` is an unvalidated string, like every other expiry in this file:
122
+ * no consumer on the JS path interprets it (native background code parses it
123
+ * itself), and a `.datetime()` here alone would leave one strict field beside
124
+ * two lax ones. If expiry is ever validated it goes on all three at once, with
125
+ * the API's serializers checked against it — the producer is the same server.
126
+ */
127
+ export const deviceBackgroundCredentialResponseSchema = z.object({
128
+ deviceId: z.string().min(1),
129
+ secret: z.string().min(1),
130
+ accountId: z.string().min(1),
131
+ expiresAt: z.string(),
132
+ });
133
+ /**
134
+ * Request body for `POST /session/device/background-token` — presented by
135
+ * native background code with NO bearer and NO cookies: possession of the
136
+ * background `secret` IS the proof, as it is for the device-secret mint.
137
+ *
138
+ * Unlike that mint this one NEVER rotates the presented secret (hence no
139
+ * `next…` field to persist in the response), so background code interrupted
140
+ * anywhere between request and response leaves the credential intact and
141
+ * usable on its next run.
142
+ */
143
+ export const deviceBackgroundTokenRequestSchema = z.object({
144
+ deviceId: z.string().min(1),
145
+ secret: z.string().min(1),
146
+ });
147
+ /**
148
+ * Wire shape of a successful `POST /session/device/background-token`: the short
149
+ * access token, its expiry, and the account the token belongs to — the last so
150
+ * a caller can key cached data per account and drop data belonging to a
151
+ * foreign one.
152
+ *
153
+ * Carries NO device state — no account list, no `activeAccountId`, no
154
+ * `revision`, unlike {@link deviceTokenMintResponseSchema} — deliberately, to
155
+ * cap what a compromised credential record yields.
156
+ */
157
+ export const deviceBackgroundTokenResponseSchema = z.object({
158
+ accessToken: z.string(),
159
+ expiresAt: z.string(),
160
+ accountId: z.string().min(1),
161
+ });
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+ export const emailContextAddressSchema = z.object({
3
+ name: z.string().optional(),
4
+ address: z.string().email(),
5
+ }).strict();
6
+ export const emailContextMailboxSchema = z.object({
7
+ mailboxId: z.string().min(1),
8
+ name: z.string(),
9
+ path: z.string(),
10
+ totalMessages: z.number().int().nonnegative(),
11
+ unseenMessages: z.number().int().nonnegative(),
12
+ }).strict();
13
+ export const emailContextMessageSchema = z.object({
14
+ messageId: z.string().min(1),
15
+ mailboxId: z.string().min(1),
16
+ from: emailContextAddressSchema,
17
+ subject: z.string(),
18
+ receivedAt: z.string().datetime(),
19
+ seen: z.boolean(),
20
+ answered: z.boolean(),
21
+ }).strict();
22
+ export const emailAgentContextSchema = z.object({
23
+ accountId: z.string().min(1),
24
+ resourceMailboxId: z.string().min(1).nullable(),
25
+ generatedAt: z.string().datetime(),
26
+ mailboxes: z.array(emailContextMailboxSchema),
27
+ recentUnread: z.array(emailContextMessageSchema),
28
+ needsResponse: z.array(emailContextMessageSchema),
29
+ }).strict();
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The follow graph wire contract (`/v2/follows`).
3
+ *
4
+ * These types are the boundary between the API that owns the graph and every
5
+ * application that reads it. They live here — not in the API and not in the
6
+ * SDK — because both ends have to agree, and a shape defined on one side is a
7
+ * shape the other side re-declares slightly differently within a release or two.
8
+ *
9
+ * ## Why the state is three fields and not a boolean
10
+ *
11
+ * A user can follow something globally and turn it off in ONE application. That
12
+ * is a state the user themselves created, so the client has to be able to see
13
+ * it and say so — "following, but not shown here" is a sentence a boolean
14
+ * cannot express. `globalState`, `applicationMode` and `effectiveState` are
15
+ * therefore reported separately, and only the last one answers "does this
16
+ * appear in my feed right now".
17
+ *
18
+ * ## Why kinds are strings
19
+ *
20
+ * `FollowTargetKind` is a plain `string`, not a union. Applications register
21
+ * their own kinds at runtime (`mercaria.store`, `syra.artist`), so a union here
22
+ * would mean every new application in the ecosystem needs a release of this
23
+ * package before it can follow anything. The namespace rule is enforced by the
24
+ * database, which is the one place that can enforce it for applications this
25
+ * package has never heard of.
26
+ */
27
+ export {};
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Self-sovereign identity API contracts.
3
+ *
4
+ * SINGLE SOURCE OF TRUTH for the wire shape of Oxy's AtProto/Bluesky-flavoured
5
+ * identity & portability layer: the W3C DID document the API derives on demand,
6
+ * the signed-record envelope clients sign with their cryptographic key (and the
7
+ * server verifies), the verified-domain badge, the auth-method ↔ DID
8
+ * verification-method mapping, and the signed data-export ("credible exit")
9
+ * bundle. The API validates its OUTPUT against these schemas; every consumer
10
+ * (the Commons vault app, `@oxy.so/core`'s identity mixin) validates its INPUT
11
+ * against the same definitions, so producer and consumers cannot drift.
12
+ *
13
+ * Design anchors (from the identity-layer plan):
14
+ * - DID = `did:web:oxy.so:u:<userId>` — anchored on the stable account id, NOT
15
+ * the keypair. The keypair is a *verification method* that maps 1:1 to the
16
+ * existing `authMethods[]`. Custodial (password-only) users get a DID
17
+ * controlled solely by Oxy (`OXY_DID`); creating a Commons key upgrades them
18
+ * to self-sovereign (`controller = [userDid, OXY_DID]`); fully reversible.
19
+ * - Verification methods use the secp256k1 `EcdsaSecp256k1VerificationKey2019`
20
+ * type with `publicKeyHex` for now (a `Multikey`/`publicKeyMultibase` form may
21
+ * be added later — see the plan's open risks).
22
+ * - Signed records carry an envelope whose signing input is the canonical-JSON
23
+ * of every field EXCEPT `publicKey` and `signature`; `alg` is
24
+ * `ES256K-DER-SHA256` (secp256k1 over the SHA-256 of the canonical bytes,
25
+ * DER-encoded signature) — the same scheme `SignatureService` uses.
26
+ *
27
+ * Explicit-`interface` exports (DidDocument, SignedRecordEnvelope, ExportBundle,
28
+ * VerifiedDomain, AuthMethodsResponse and their sub-parts) follow the same
29
+ * rationale as `UserNameResponse` in `./userResponse`: a `z.infer<>` of a nested
30
+ * object schema can degrade under a consumer's `moduleResolution: "node"`
31
+ * (node10) resolution, so the load-bearing response shapes are declared as
32
+ * literal interfaces and the runtime schemas are annotated `z.ZodType<Interface>`
33
+ * — the emitted `.d.ts` then states the field types verbatim and survives BOTH
34
+ * `node` and `bundler` resolution. Request schemas (no nested-object hazard) are
35
+ * inferred via `z.infer<>`.
36
+ *
37
+ * Platform-agnostic — zod only, no react/react-native/expo. ESM-safe (no
38
+ * `require()`).
39
+ */
40
+ import { z } from 'zod';
41
+ // The option schemas are left UN-annotated so they keep their concrete
42
+ // `ZodObject` type — `z.discriminatedUnion` requires object options and an
43
+ // explicit `z.ZodType<>` annotation would erase the shape it discriminates on.
44
+ // `z.object` already infers each option's type exactly (id/type/controller +
45
+ // the key field), so the union is structurally `VerificationMethod`.
46
+ const secp256k1VerificationMethodSchema = z.object({
47
+ id: z.string(),
48
+ type: z.literal('EcdsaSecp256k1VerificationKey2019'),
49
+ controller: z.string(),
50
+ publicKeyHex: z.string(),
51
+ });
52
+ const multikeyVerificationMethodSchema = z.object({
53
+ id: z.string(),
54
+ type: z.literal('Multikey'),
55
+ controller: z.string(),
56
+ publicKeyMultibase: z.string(),
57
+ });
58
+ export const verificationMethodSchema = z.discriminatedUnion('type', [
59
+ secp256k1VerificationMethodSchema,
60
+ multikeyVerificationMethodSchema,
61
+ ]);
62
+ export const didServiceSchema = z.object({
63
+ id: z.string(),
64
+ type: z.string(),
65
+ serviceEndpoint: z.string(),
66
+ });
67
+ export const didDocumentSchema = z.object({
68
+ '@context': z.array(z.string()),
69
+ id: z.string(),
70
+ controller: z.array(z.string()),
71
+ verificationMethod: z.array(verificationMethodSchema),
72
+ authentication: z.array(z.string()),
73
+ assertionMethod: z.array(z.string()),
74
+ alsoKnownAs: z.array(z.string()),
75
+ service: z.array(didServiceSchema),
76
+ });
77
+ export const signedRecordEnvelopeSchema = z
78
+ .object({
79
+ version: z.union([z.literal(1), z.literal(2)]),
80
+ // Open, app-defined category (see the `type` doc above). The Oxy STORE
81
+ // re-narrows to `oxySignedRecordTypeSchema`; an app to its own constant.
82
+ type: z.string().min(1),
83
+ subject: z.string(),
84
+ issuer: z.string(),
85
+ record: z.record(z.unknown()),
86
+ issuedAt: z.number(),
87
+ seq: z.number().int().nonnegative().optional(),
88
+ prev: z.string().nullable().optional(),
89
+ collection: z.string().min(1).optional(),
90
+ rkey: z.string().min(1).optional(),
91
+ publicKey: z.string(),
92
+ alg: z.literal('ES256K-DER-SHA256'),
93
+ signature: z.string(),
94
+ })
95
+ .superRefine((env, ctx) => {
96
+ if (env.version === 2) {
97
+ // v2 REQUIRES the hash-chain fields. `prev` may be `null` at genesis,
98
+ // but the key must be present (it is part of the signed bytes), so we
99
+ // reject only when it is entirely absent.
100
+ if (typeof env.seq !== 'number') {
101
+ ctx.addIssue({
102
+ code: z.ZodIssueCode.custom,
103
+ message: 'v2 envelope requires `seq`',
104
+ path: ['seq'],
105
+ });
106
+ }
107
+ if (env.prev === undefined) {
108
+ ctx.addIssue({
109
+ code: z.ZodIssueCode.custom,
110
+ message: 'v2 envelope requires `prev` (use `null` at genesis)',
111
+ path: ['prev'],
112
+ });
113
+ }
114
+ if (typeof env.collection !== 'string') {
115
+ ctx.addIssue({
116
+ code: z.ZodIssueCode.custom,
117
+ message: 'v2 envelope requires `collection`',
118
+ path: ['collection'],
119
+ });
120
+ }
121
+ if (typeof env.rkey !== 'string') {
122
+ ctx.addIssue({
123
+ code: z.ZodIssueCode.custom,
124
+ message: 'v2 envelope requires `rkey`',
125
+ path: ['rkey'],
126
+ });
127
+ }
128
+ }
129
+ else {
130
+ // v1 FORBIDS the v2 chain fields entirely, so a legacy envelope keeps
131
+ // its exact byte shape and cannot smuggle unsigned chain metadata.
132
+ if (env.seq !== undefined) {
133
+ ctx.addIssue({
134
+ code: z.ZodIssueCode.custom,
135
+ message: 'v1 envelope must not carry `seq`',
136
+ path: ['seq'],
137
+ });
138
+ }
139
+ if (env.prev !== undefined) {
140
+ ctx.addIssue({
141
+ code: z.ZodIssueCode.custom,
142
+ message: 'v1 envelope must not carry `prev`',
143
+ path: ['prev'],
144
+ });
145
+ }
146
+ if (env.collection !== undefined) {
147
+ ctx.addIssue({
148
+ code: z.ZodIssueCode.custom,
149
+ message: 'v1 envelope must not carry `collection`',
150
+ path: ['collection'],
151
+ });
152
+ }
153
+ if (env.rkey !== undefined) {
154
+ ctx.addIssue({
155
+ code: z.ZodIssueCode.custom,
156
+ message: 'v1 envelope must not carry `rkey`',
157
+ path: ['rkey'],
158
+ });
159
+ }
160
+ }
161
+ });
162
+ export const verifiedDomainSchema = z.object({
163
+ domain: z.string(),
164
+ verifiedAt: z.union([z.string(), z.date()]),
165
+ method: z.enum(['dns-txt', 'well-known']),
166
+ });
167
+ /** Request body for `POST /identity/domains` — the domain to start verifying. */
168
+ export const domainVerificationRequestSchema = z.object({
169
+ domain: z.string().trim().min(1),
170
+ });
171
+ /**
172
+ * The instructions the API returns when a domain verification is requested. The
173
+ * caller may prove ownership EITHER by publishing the `dns` TXT record OR by
174
+ * serving the `wellKnown` file; either path then satisfies
175
+ * `POST /identity/domains/:domain/verify`.
176
+ */
177
+ export const domainVerificationInstructionsSchema = z.object({
178
+ domain: z.string(),
179
+ token: z.string(),
180
+ dns: z.object({
181
+ name: z.string(),
182
+ value: z.string(),
183
+ }),
184
+ wellKnown: z.object({
185
+ url: z.string(),
186
+ body: z.string(),
187
+ }),
188
+ });
189
+ export const authMethodEntrySchema = z.object({
190
+ type: z.enum(['identity', 'webauthn']),
191
+ linkedAt: z.union([z.string(), z.date()]),
192
+ verificationMethodId: z.string().optional(),
193
+ credentialId: z.string().optional(),
194
+ name: z.string().optional(),
195
+ });
196
+ export const authMethodsResponseSchema = z.object({
197
+ did: z.string(),
198
+ methods: z.array(authMethodEntrySchema),
199
+ });
200
+ export const exportAttestationSchema = z.object({
201
+ issuer: z.string(),
202
+ publicKey: z.string(),
203
+ alg: z.literal('ES256K-DER-SHA256'),
204
+ signature: z.string(),
205
+ signedAt: z.number(),
206
+ });
207
+ export const exportUsageReceiptSchema = z.object({
208
+ receiptId: z.string(),
209
+ requestId: z.string(),
210
+ settledAt: z.string(),
211
+ billedAmount: z.string(),
212
+ currency: z.string(),
213
+ outcome: z.string(),
214
+ resolvedModelReference: z.string(),
215
+ servingProvider: z.string(),
216
+ platformFeeOnly: z.boolean(),
217
+ });
218
+ export const exportLedgerEntrySchema = z.object({
219
+ entryId: z.string(),
220
+ kind: z.string(),
221
+ currency: z.string(),
222
+ createdAt: z.string(),
223
+ });
224
+ export const exportUsageReservationSchema = z.object({
225
+ reservationId: z.string(),
226
+ requestId: z.string(),
227
+ status: z.string(),
228
+ reservedAmount: z.string(),
229
+ currency: z.string(),
230
+ createdAt: z.string(),
231
+ expiresAt: z.string(),
232
+ });
233
+ export const exportFinancialSectionSchema = z.object({
234
+ receipts: z.array(exportUsageReceiptSchema),
235
+ ledgerEntries: z.array(exportLedgerEntrySchema),
236
+ reservations: z.array(exportUsageReservationSchema),
237
+ });
238
+ export const exportBundleSchema = z.object({
239
+ '$schema': z.string(),
240
+ exportedAt: z.string(),
241
+ did: z.string(),
242
+ didDocument: didDocumentSchema,
243
+ profile: z.record(z.unknown()),
244
+ verifiedDomains: z.array(verifiedDomainSchema),
245
+ authMethods: z.array(authMethodEntrySchema),
246
+ signedRecords: z.array(signedRecordEnvelopeSchema),
247
+ appData: z.array(z.record(z.unknown())),
248
+ social: z.object({
249
+ following: z.array(z.string()),
250
+ followers: z.array(z.string()),
251
+ }),
252
+ financial: exportFinancialSectionSchema,
253
+ attestation: exportAttestationSchema.nullable(),
254
+ proof: exportAttestationSchema.optional(),
255
+ });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Canonical contract for Inbox new-mail push notifications.
3
+ *
4
+ * The Android channel id and payload `type` are wire contracts: Android 8+
5
+ * drops a notification whose channel the app has not created, and the client
6
+ * only routes taps it recognises. Two hand-typed copies of either string fail as
7
+ * "the notification never arrived" or "tapping does nothing" — the hardest push
8
+ * symptoms to diagnose.
9
+ *
10
+ * Platform-agnostic — zod only, no react/react-native/expo.
11
+ */
12
+ import { z } from 'zod';
13
+ /** Android notification channel id the new-mail push is sent on. */
14
+ export const INBOX_EMAIL_PUSH_CHANNEL = 'email';
15
+ /** Runtime type discriminator of the new-mail push payload. */
16
+ export const INBOX_EMAIL_PUSH_TYPE = 'oxy_inbox_new_message';
17
+ export const inboxEmailPushDataSchema = z.object({
18
+ type: z.literal(INBOX_EMAIL_PUSH_TYPE),
19
+ messageId: z.string().min(1),
20
+ mailboxId: z.string().min(1),
21
+ });