@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.
- package/LICENSE +202 -0
- package/NOTICE +16 -0
- package/dist/cjs/.tsbuildinfo +1 -0
- package/dist/cjs/accountGraph.js +489 -0
- package/dist/cjs/agency.js +439 -0
- package/dist/cjs/browserHub.js +215 -0
- package/dist/cjs/civic.js +163 -0
- package/dist/cjs/commonsSignIn.js +59 -0
- package/dist/cjs/deviceBoot.js +50 -0
- package/dist/cjs/deviceDirectory.js +189 -0
- package/dist/cjs/devicePairing.js +138 -0
- package/dist/cjs/deviceSession.js +164 -0
- package/dist/cjs/emailAgentContext.js +32 -0
- package/dist/cjs/followGraph.js +28 -0
- package/dist/cjs/identity.js +258 -0
- package/dist/cjs/inboxPush.js +24 -0
- package/dist/cjs/index.js +618 -0
- package/dist/cjs/inference/accountBilling.js +334 -0
- package/dist/cjs/inference/aliaModelRelease.js +262 -0
- package/dist/cjs/inference/attribution.js +106 -0
- package/dist/cjs/inference/catalogue.js +487 -0
- package/dist/cjs/inference/entitlement.js +217 -0
- package/dist/cjs/inference/errors.js +309 -0
- package/dist/cjs/inference/identifiers.js +224 -0
- package/dist/cjs/inference/inbox.js +105 -0
- package/dist/cjs/inference/modelDocumentation.js +433 -0
- package/dist/cjs/inference/money.js +188 -0
- package/dist/cjs/inference/priceVersion.js +110 -0
- package/dist/cjs/inference/providerConnection.js +455 -0
- package/dist/cjs/inference/request.js +477 -0
- package/dist/cjs/inference/routingPolicy.js +318 -0
- package/dist/cjs/inference/streamEvents.js +258 -0
- package/dist/cjs/inference/usage.js +329 -0
- package/dist/cjs/inference/version.js +105 -0
- package/dist/cjs/keyRecovery.js +91 -0
- package/dist/cjs/keyRotation.js +75 -0
- package/dist/cjs/links.js +68 -0
- package/dist/cjs/moderationReputation.js +298 -0
- package/dist/cjs/oauth.js +66 -0
- package/dist/cjs/oxyRecordTypes.js +71 -0
- package/dist/cjs/protocol.js +53 -0
- package/dist/cjs/recommendations.js +168 -0
- package/dist/cjs/reputation.js +297 -0
- package/dist/cjs/sessionStatus.js +121 -0
- package/dist/cjs/transparency.js +89 -0
- package/dist/cjs/updates.js +252 -0
- package/dist/cjs/userInvalidation.js +89 -0
- package/dist/cjs/userResponse.js +245 -0
- package/dist/cjs/username.js +290 -0
- package/dist/cjs/webauthn.js +71 -0
- package/dist/esm/.tsbuildinfo +1 -0
- package/dist/esm/accountGraph.js +480 -0
- package/dist/esm/agency.js +436 -0
- package/dist/esm/browserHub.js +212 -0
- package/dist/esm/civic.js +160 -0
- package/dist/esm/commonsSignIn.js +56 -0
- package/dist/esm/deviceBoot.js +47 -0
- package/dist/esm/deviceDirectory.js +186 -0
- package/dist/esm/devicePairing.js +135 -0
- package/dist/esm/deviceSession.js +161 -0
- package/dist/esm/emailAgentContext.js +29 -0
- package/dist/esm/followGraph.js +27 -0
- package/dist/esm/identity.js +255 -0
- package/dist/esm/inboxPush.js +21 -0
- package/dist/esm/index.js +172 -0
- package/dist/esm/inference/accountBilling.js +331 -0
- package/dist/esm/inference/aliaModelRelease.js +259 -0
- package/dist/esm/inference/attribution.js +103 -0
- package/dist/esm/inference/catalogue.js +484 -0
- package/dist/esm/inference/entitlement.js +214 -0
- package/dist/esm/inference/errors.js +306 -0
- package/dist/esm/inference/identifiers.js +221 -0
- package/dist/esm/inference/inbox.js +102 -0
- package/dist/esm/inference/modelDocumentation.js +430 -0
- package/dist/esm/inference/money.js +185 -0
- package/dist/esm/inference/priceVersion.js +107 -0
- package/dist/esm/inference/providerConnection.js +452 -0
- package/dist/esm/inference/request.js +474 -0
- package/dist/esm/inference/routingPolicy.js +315 -0
- package/dist/esm/inference/streamEvents.js +255 -0
- package/dist/esm/inference/usage.js +326 -0
- package/dist/esm/inference/version.js +102 -0
- package/dist/esm/keyRecovery.js +88 -0
- package/dist/esm/keyRotation.js +72 -0
- package/dist/esm/links.js +65 -0
- package/dist/esm/moderationReputation.js +295 -0
- package/dist/esm/oauth.js +63 -0
- package/dist/esm/oxyRecordTypes.js +68 -0
- package/dist/esm/protocol.js +50 -0
- package/dist/esm/recommendations.js +165 -0
- package/dist/esm/reputation.js +293 -0
- package/dist/esm/sessionStatus.js +118 -0
- package/dist/esm/transparency.js +86 -0
- package/dist/esm/updates.js +249 -0
- package/dist/esm/userInvalidation.js +85 -0
- package/dist/esm/userResponse.js +240 -0
- package/dist/esm/username.js +283 -0
- package/dist/esm/webauthn.js +68 -0
- package/dist/types/.tsbuildinfo +1 -0
- package/dist/types/accountGraph.d.ts +378 -0
- package/dist/types/agency.d.ts +2162 -0
- package/dist/types/browserHub.d.ts +856 -0
- package/dist/types/civic.d.ts +338 -0
- package/dist/types/commonsSignIn.d.ts +58 -0
- package/dist/types/deviceBoot.d.ts +74 -0
- package/dist/types/deviceDirectory.d.ts +1317 -0
- package/dist/types/devicePairing.d.ts +130 -0
- package/dist/types/deviceSession.d.ts +411 -0
- package/dist/types/emailAgentContext.d.ts +248 -0
- package/dist/types/followGraph.d.ts +150 -0
- package/dist/types/identity.d.ts +402 -0
- package/dist/types/inboxPush.d.ts +30 -0
- package/dist/types/index.d.ts +100 -0
- package/dist/types/inference/accountBilling.d.ts +738 -0
- package/dist/types/inference/aliaModelRelease.d.ts +609 -0
- package/dist/types/inference/attribution.d.ts +176 -0
- package/dist/types/inference/catalogue.d.ts +1618 -0
- package/dist/types/inference/entitlement.d.ts +519 -0
- package/dist/types/inference/errors.d.ts +242 -0
- package/dist/types/inference/identifiers.d.ts +182 -0
- package/dist/types/inference/inbox.d.ts +374 -0
- package/dist/types/inference/modelDocumentation.d.ts +1603 -0
- package/dist/types/inference/money.d.ts +185 -0
- package/dist/types/inference/priceVersion.d.ts +182 -0
- package/dist/types/inference/providerConnection.d.ts +968 -0
- package/dist/types/inference/request.d.ts +2800 -0
- package/dist/types/inference/routingPolicy.d.ts +616 -0
- package/dist/types/inference/streamEvents.d.ts +950 -0
- package/dist/types/inference/usage.d.ts +1164 -0
- package/dist/types/inference/version.d.ts +102 -0
- package/dist/types/keyRecovery.d.ts +138 -0
- package/dist/types/keyRotation.d.ts +103 -0
- package/dist/types/links.d.ts +96 -0
- package/dist/types/moderationReputation.d.ts +487 -0
- package/dist/types/oauth.d.ts +86 -0
- package/dist/types/oxyRecordTypes.d.ts +62 -0
- package/dist/types/protocol.d.ts +86 -0
- package/dist/types/recommendations.d.ts +542 -0
- package/dist/types/reputation.d.ts +457 -0
- package/dist/types/sessionStatus.d.ts +231 -0
- package/dist/types/transparency.d.ts +392 -0
- package/dist/types/updates.d.ts +545 -0
- package/dist/types/userInvalidation.d.ts +94 -0
- package/dist/types/userResponse.d.ts +1706 -0
- package/dist/types/username.d.ts +265 -0
- package/dist/types/webauthn.d.ts +77 -0
- 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
|
+
});
|