@oxy.so/contracts 2.1.0 → 3.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 (57) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/accountEmail.js +79 -0
  3. package/dist/cjs/deviceBoot.js +2 -2
  4. package/dist/cjs/deviceSession.js +73 -16
  5. package/dist/cjs/identity.js +1 -3
  6. package/dist/cjs/identityLink.js +92 -0
  7. package/dist/cjs/identityProof.js +6 -29
  8. package/dist/cjs/index.js +82 -65
  9. package/dist/cjs/reputation.js +10 -55
  10. package/dist/cjs/signIn.js +304 -0
  11. package/dist/esm/.tsbuildinfo +1 -1
  12. package/dist/esm/accountEmail.js +76 -0
  13. package/dist/esm/deviceBoot.js +2 -2
  14. package/dist/esm/deviceSession.js +72 -15
  15. package/dist/esm/identity.js +1 -3
  16. package/dist/esm/identityLink.js +87 -0
  17. package/dist/esm/identityProof.js +6 -29
  18. package/dist/esm/index.js +13 -16
  19. package/dist/esm/reputation.js +9 -54
  20. package/dist/esm/signIn.js +299 -0
  21. package/dist/types/.tsbuildinfo +1 -1
  22. package/dist/types/accountEmail.d.ts +90 -0
  23. package/dist/types/agency.d.ts +2 -2
  24. package/dist/types/deviceBoot.d.ts +2 -2
  25. package/dist/types/deviceSession.d.ts +131 -19
  26. package/dist/types/externalIdentity.d.ts +4 -4
  27. package/dist/types/identity.d.ts +4 -8
  28. package/dist/types/identityLink.d.ts +146 -0
  29. package/dist/types/identityProof.d.ts +15 -42
  30. package/dist/types/index.d.ts +10 -12
  31. package/dist/types/inference/accountBilling.d.ts +2 -2
  32. package/dist/types/inference/embeddings.d.ts +16 -16
  33. package/dist/types/inference/entitlement.d.ts +2 -2
  34. package/dist/types/inference/errors.d.ts +8 -8
  35. package/dist/types/inference/inbox.d.ts +8 -8
  36. package/dist/types/inference/providerConnection.d.ts +20 -20
  37. package/dist/types/inference/request.d.ts +16 -16
  38. package/dist/types/inference/streamEvents.d.ts +20 -20
  39. package/dist/types/keyRecovery.d.ts +4 -4
  40. package/dist/types/oauth.d.ts +16 -16
  41. package/dist/types/reputation.d.ts +32 -127
  42. package/dist/types/sessionStatus.d.ts +2 -2
  43. package/dist/types/signIn.d.ts +717 -0
  44. package/dist/types/userResponse.d.ts +2 -2
  45. package/package.json +1 -1
  46. package/dist/cjs/identityMove.js +0 -156
  47. package/dist/cjs/identityRecovery.js +0 -51
  48. package/dist/cjs/webIdentityCarrier.js +0 -181
  49. package/dist/cjs/webauthn.js +0 -85
  50. package/dist/esm/identityMove.js +0 -148
  51. package/dist/esm/identityRecovery.js +0 -48
  52. package/dist/esm/webIdentityCarrier.js +0 -178
  53. package/dist/esm/webauthn.js +0 -82
  54. package/dist/types/identityMove.d.ts +0 -185
  55. package/dist/types/identityRecovery.d.ts +0 -246
  56. package/dist/types/webIdentityCarrier.d.ts +0 -1130
  57. package/dist/types/webauthn.d.ts +0 -285
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EMAIL_VERIFICATION_ERROR_CODES = exports.emailVerificationConfirmResponseSchema = exports.emailVerificationConfirmRequestSchema = exports.emailVerificationStartResponseSchema = exports.emailVerificationStartRequestSchema = exports.emailTicketSchema = exports.emailAddressSchema = exports.EMAIL_TICKET_TTL_MS = exports.EMAIL_CODE_MAX_ATTEMPTS = exports.EMAIL_CODE_TTL_MS = exports.EMAIL_CODE_LENGTH = exports.EMAIL_VERIFICATION_PURPOSES = void 0;
4
+ /**
5
+ * Email codes and tickets (ADR 0030).
6
+ *
7
+ * A web account is a username and an email; a password and an authenticator
8
+ * app are optional and added later. The email is proven by a 6-digit code sent
9
+ * to it, and the proof is a short-lived one-use ticket the next step spends:
10
+ *
11
+ * - `signup`: the ticket lets `POST /auth/signup` create the account with that
12
+ * email. `start` answers the same whether or not the address already has an
13
+ * account, so it never tells anyone which emails have an Oxy account.
14
+ *
15
+ * Signing in by email (`signin`) and re-verifying before a sensitive change
16
+ * (`reauth`) use the same code, through their own routes (`signIn.ts`).
17
+ */
18
+ const zod_1 = require("zod");
19
+ /**
20
+ * - `signup`: above.
21
+ * - `signin`: the code (and link) of an email sign-in (`POST /auth/signin/email/start`).
22
+ * - `reauth`: a signed-in person proving it is them before a sensitive step
23
+ * (`POST /users/me/reauth/email`): a password, an authenticator, deleting the
24
+ * account, linking Commons.
25
+ */
26
+ exports.EMAIL_VERIFICATION_PURPOSES = ['signup', 'signin', 'reauth'];
27
+ /** Digits in a code. */
28
+ exports.EMAIL_CODE_LENGTH = 6;
29
+ /** How long a code can be confirmed. */
30
+ exports.EMAIL_CODE_TTL_MS = 10 * 60 * 1000;
31
+ /** Wrong codes before a verification is spent and a new code is needed. */
32
+ exports.EMAIL_CODE_MAX_ATTEMPTS = 5;
33
+ /** How long a confirmed code's ticket can be spent. */
34
+ exports.EMAIL_TICKET_TTL_MS = 15 * 60 * 1000;
35
+ /** An email address as the API stores it: trimmed and lowercase. */
36
+ exports.emailAddressSchema = zod_1.z.string().trim().toLowerCase().min(3).max(254).email();
37
+ /** An opaque one-use ticket (32 random bytes, base64url). */
38
+ exports.emailTicketSchema = zod_1.z
39
+ .string()
40
+ .trim()
41
+ .regex(/^[A-Za-z0-9_-]{43}$/, 'ticket must be 32 bytes of base64url');
42
+ /** `POST /auth/email/verify/start` */
43
+ exports.emailVerificationStartRequestSchema = zod_1.z
44
+ .object({ purpose: zod_1.z.literal('signup'), email: exports.emailAddressSchema })
45
+ .strict();
46
+ exports.emailVerificationStartResponseSchema = zod_1.z.object({
47
+ verificationId: zod_1.z.string().min(1).max(64),
48
+ expiresAt: zod_1.z.number().int().positive(),
49
+ });
50
+ /** `POST /auth/email/verify/confirm` */
51
+ exports.emailVerificationConfirmRequestSchema = zod_1.z
52
+ .object({
53
+ verificationId: zod_1.z.string().trim().min(1).max(64),
54
+ code: zod_1.z
55
+ .string()
56
+ .trim()
57
+ .regex(new RegExp(`^\\d{${exports.EMAIL_CODE_LENGTH}}$`), `code must be ${exports.EMAIL_CODE_LENGTH} digits`),
58
+ })
59
+ .strict();
60
+ exports.emailVerificationConfirmResponseSchema = zod_1.z.object({
61
+ ticket: exports.emailTicketSchema,
62
+ expiresAt: zod_1.z.number().int().positive(),
63
+ });
64
+ /**
65
+ * Stable error codes (`error.code` in the API error body). Clients map these
66
+ * through their localization, never the English message.
67
+ */
68
+ exports.EMAIL_VERIFICATION_ERROR_CODES = {
69
+ /** The code is wrong, or its verification expired or was spent. */
70
+ codeInvalid: 'EMAIL_CODE_INVALID',
71
+ /** Too many wrong codes: request a new one. */
72
+ tooManyAttempts: 'EMAIL_CODE_TOO_MANY_ATTEMPTS',
73
+ /** The ticket is unknown, expired, spent, or for another email or purpose. */
74
+ ticketInvalid: 'EMAIL_TICKET_INVALID',
75
+ /** A sign-up without a confirmed email. */
76
+ ticketRequired: 'EMAIL_TICKET_REQUIRED',
77
+ /** This server cannot send mail. */
78
+ unavailable: 'EMAIL_UNAVAILABLE',
79
+ };
@@ -5,8 +5,8 @@
5
5
  * SINGLE SOURCE OF TRUTH for the first-party login result (the session arm). The
6
6
  * API validates its OUTPUT against this schema; every consumer (`@oxy.so/core`'s
7
7
  * auth mixin) validates its INPUT against the same definition, so producer and
8
- * consumers cannot drift. Sign-in is passkey (WebAuthn) or Commons handoff —
9
- * password and 2FA were removed, so the only outcome is a completed session.
8
+ * consumers cannot drift. This is the session arm every sign-in
9
+ * ends in (email code or link, password, authenticator, Commons handoff).
10
10
  *
11
11
  * The device transport is `deviceId` + `deviceSecret` + `POST /session/device/token`
12
12
  * (see `deviceSession.ts`). The legacy cookie/bootstrap/refresh-family lanes were
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deviceBackgroundTokenResponseSchema = exports.deviceBackgroundTokenRequestSchema = exports.deviceBackgroundCredentialResponseSchema = exports.sessionAccountsChangedEventSchema = exports.sessionAccountsChangedReasonSchema = exports.SESSION_ACCOUNTS_CHANGED_EVENT = exports.deviceTokenMintResponseSchema = exports.deviceTokenMintRequestSchema = exports.deviceSessionSyncSchema = exports.activeTokenSchema = exports.deviceSessionStateSchema = exports.sessionAccountSchema = void 0;
3
+ exports.deviceJoinResponseSchema = exports.deviceJoinRequestSchema = exports.deviceJoinCodeResponseSchema = exports.deviceJoinCodeRequestSchema = exports.deviceRegisterResponseSchema = exports.deviceProofSchema = exports.deviceBackgroundTokenResponseSchema = exports.deviceBackgroundTokenRequestSchema = exports.deviceBackgroundCredentialResponseSchema = exports.sessionAccountsChangedEventSchema = exports.sessionAccountsChangedReasonSchema = exports.SESSION_ACCOUNTS_CHANGED_EVENT = exports.deviceTokenMintResponseSchema = exports.deviceTokenMintRequestSchema = exports.deviceSessionSyncSchema = exports.activeTokenSchema = exports.deviceSessionStateSchema = exports.sessionAccountSchema = void 0;
4
4
  const zod_1 = require("zod");
5
5
  exports.sessionAccountSchema = zod_1.z.object({
6
6
  accountId: zod_1.z.string(),
@@ -29,9 +29,10 @@ exports.deviceSessionSyncSchema = zod_1.z.object({
29
29
  /**
30
30
  * Request body for `POST /session/device/token` — the client presents the
31
31
  * `deviceId` it stored first-party plus the opaque `deviceSecret`. NO bearer:
32
- * possession of the secret IS the proof of device ownership. The server matches
33
- * `sha256(deviceSecret)` against the device's stored `secretHash` (constant-time)
34
- * and mints a short access token for the device's active account.
32
+ * possession of the secret IS the proof of device ownership. The server looks
33
+ * `sha256(deviceSecret)` up among the device's holder credentials (one per app
34
+ * or origin that joined the shared DeviceSession) and mints a short access
35
+ * token for the device's active account.
35
36
  *
36
37
  * `accountId` pins the mint to ONE account of that device instead of whichever
37
38
  * account is currently active. It exists for identity-bound clients (Commons),
@@ -51,8 +52,8 @@ exports.deviceTokenMintRequestSchema = zod_1.z.object({
51
52
  * short access token for the active account, its expiry, the device secret the
52
53
  * client must persist (`nextDeviceSecret` — on mint this echoes the presented
53
54
  * secret unchanged so concurrent refreshes from multiple origins do not race),
54
- * and the projected device-session state. Sign-in rotates the secret via
55
- * `issueDeviceSecret`; mint does not.
55
+ * and the projected device-session state. Nothing rotates: each sign-in issues
56
+ * a NEW holder credential and leaves the others valid.
56
57
  */
57
58
  exports.deviceTokenMintResponseSchema = zod_1.z.object({
58
59
  accessToken: zod_1.z.string(),
@@ -110,12 +111,11 @@ exports.sessionAccountsChangedEventSchema = zod_1.z.object({
110
111
  * derived server-side from it) and consumed afterwards only by native
111
112
  * background code, which has no JS runtime to mint a token for itself.
112
113
  *
113
- * Deliberately a SEPARATE credential from the rotating `deviceSecret`: that one
114
- * rotates on every mint, so background code presenting it would become a second
115
- * writer of a value the JS runtime depends on, and background code killed
116
- * mid-rotation would silently sign the user out on the next cold start. Against
117
- * this credential background code is the sole writer, and it can never rotate
118
- * anything JS reads.
114
+ * Deliberately a SEPARATE credential from the holder `deviceSecret`: that one
115
+ * is device-wide and mints for whichever account is active, while this one is
116
+ * bound to ONE account and expires, so a widget worker never holds a
117
+ * credential that reaches every account on the device. Background code is its
118
+ * sole writer and never touches anything JS reads.
119
119
  *
120
120
  * The raw `secret` is returned exactly once, at provision time — never stored
121
121
  * retrievably, never logged, never re-read. A caller that loses it provisions
@@ -138,10 +138,10 @@ exports.deviceBackgroundCredentialResponseSchema = zod_1.z.object({
138
138
  * native background code with NO bearer and NO cookies: possession of the
139
139
  * background `secret` IS the proof, as it is for the device-secret mint.
140
140
  *
141
- * Unlike that mint this one NEVER rotates the presented secret (hence no
142
- * `next…` field to persist in the response), so background code interrupted
143
- * anywhere between request and response leaves the credential intact and
144
- * usable on its next run.
141
+ * Like that mint this one NEVER rotates the presented secret, and it carries
142
+ * no `next…` field at all, so background code interrupted anywhere between
143
+ * request and response leaves the credential intact and usable on its next
144
+ * run.
145
145
  */
146
146
  exports.deviceBackgroundTokenRequestSchema = zod_1.z.object({
147
147
  deviceId: zod_1.z.string().min(1),
@@ -162,3 +162,60 @@ exports.deviceBackgroundTokenResponseSchema = zod_1.z.object({
162
162
  expiresAt: zod_1.z.string(),
163
163
  accountId: zod_1.z.string().min(1),
164
164
  });
165
+ /* -------------------------------------------------------------------------- */
166
+ /* The browser bridge — joining the browser's DeviceSession (ADR 0029 D2) */
167
+ /* -------------------------------------------------------------------------- */
168
+ /**
169
+ * Proof that the caller holds a device: the `deviceId` it stored first-party and
170
+ * one of that device's holder secrets. Sent with `POST /session/device/join-code`
171
+ * and, optionally, with a sign-in (`POST /auth/session/claim`, the email,
172
+ * password and second-factor sign-ins, `POST /auth/signup`), where
173
+ * a valid proof puts the new session on THAT device so every app holding it sees
174
+ * the account. An invalid proof on a sign-in is ignored, never an error.
175
+ */
176
+ exports.deviceProofSchema = zod_1.z.object({
177
+ deviceId: zod_1.z.string().min(1).max(128),
178
+ deviceSecret: zod_1.z.string().min(1).max(256),
179
+ });
180
+ /**
181
+ * `POST /session/device/register` — auth.oxy.so only. No body. A new, empty
182
+ * DeviceSession with a server-chosen `deviceId` and ONE holder credential for
183
+ * auth.oxy.so. The raw secret is returned exactly once.
184
+ */
185
+ exports.deviceRegisterResponseSchema = zod_1.z.object({
186
+ deviceId: zod_1.z.string().min(1),
187
+ deviceSecret: zod_1.z.string().min(1),
188
+ });
189
+ /** PKCE S256 challenge: base64url of a SHA-256 digest (43 characters). */
190
+ const pkceS256ChallengeSchema = zod_1.z.string().regex(/^[A-Za-z0-9_-]{43}$/);
191
+ /**
192
+ * `POST /session/device/join-code` — auth.oxy.so only (the bridge window). Proves
193
+ * auth.oxy.so's device and asks for a one-use code an official app redeems to
194
+ * join it. The code is bound to the application (`clientId`), its exact
195
+ * registered `redirectUri` and the app's PKCE challenge, and lives about a
196
+ * minute.
197
+ */
198
+ exports.deviceJoinCodeRequestSchema = exports.deviceProofSchema.extend({
199
+ clientId: zod_1.z.string().min(1).max(256),
200
+ redirectUri: zod_1.z.string().url().max(2048),
201
+ codeChallenge: pkceS256ChallengeSchema,
202
+ codeChallengeMethod: zod_1.z.literal('S256'),
203
+ });
204
+ exports.deviceJoinCodeResponseSchema = zod_1.z.object({
205
+ code: zod_1.z.string().min(1),
206
+ /** Seconds until the code expires. */
207
+ expiresIn: zod_1.z.number().int().positive(),
208
+ });
209
+ /**
210
+ * `POST /session/device/join` — called by the app's own origin with the code the
211
+ * bridge window posted to it and the PKCE verifier only the app holds. Returns a
212
+ * NEW holder credential for the browser's device; the app then mints through the
213
+ * ordinary `POST /session/device/token`.
214
+ */
215
+ exports.deviceJoinRequestSchema = zod_1.z.object({
216
+ code: zod_1.z.string().min(1).max(256),
217
+ codeVerifier: zod_1.z.string().min(43).max(128).regex(/^[A-Za-z0-9._~-]+$/),
218
+ clientId: zod_1.z.string().min(1).max(256),
219
+ redirectUri: zod_1.z.string().url().max(2048),
220
+ });
221
+ exports.deviceJoinResponseSchema = exports.deviceRegisterResponseSchema;
@@ -210,11 +210,9 @@ exports.domainVerificationInstructionsSchema = zod_1.z.object({
210
210
  }),
211
211
  });
212
212
  exports.authMethodEntrySchema = zod_1.z.object({
213
- type: zod_1.z.enum(['identity', 'webauthn']),
213
+ type: zod_1.z.literal('identity'),
214
214
  linkedAt: zod_1.z.union([zod_1.z.string(), zod_1.z.date()]),
215
215
  verificationMethodId: zod_1.z.string().optional(),
216
- credentialId: zod_1.z.string().optional(),
217
- name: zod_1.z.string().optional(),
218
216
  });
219
217
  exports.authMethodsResponseSchema = zod_1.z.object({
220
218
  did: zod_1.z.string(),
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.identityLinkCompleteRequestSchema = exports.identityLinkProofRequestSchema = exports.identityLinkStateSchema = exports.identityLinkCreateResponseSchema = exports.identityLinkIdSchema = exports.IDENTITY_LINK_QR_PREFIX = exports.IDENTITY_LINK_STATUSES = void 0;
4
+ exports.buildIdentityLinkQrPayload = buildIdentityLinkQrPayload;
5
+ exports.parseIdentityLinkQrPayload = parseIdentityLinkQrPayload;
6
+ /**
7
+ * Linking Commons to an account without a key (ADR 0029 D3) — the two-device
8
+ * relay.
9
+ *
10
+ * An account without a key links a Commons root once, and becomes
11
+ * self-custodied: its email is deleted and its phrase in Commons is how it gets
12
+ * back in. The authority is the same as `POST /auth/link` (ADR 0024 D8): a root
13
+ * proof (`link_identity`) by the key Commons holds over a one-use challenge,
14
+ * and the account's own confirmation — a code just sent to its email (plus its
15
+ * authenticator code when it has one). Only the transport is new, because the
16
+ * two factors live on two devices:
17
+ *
18
+ * 1. The signed-in account (the "Link Commons" panel of `@oxy.so/services`)
19
+ * opens a link request → `{ linkId, challenge }`, shown as a QR
20
+ * (`oxycommons://link?id=…&c=…`).
21
+ * 2. Commons scans it, reads the request (the account's id and username),
22
+ * signs the root proof over the challenge and posts it with its key.
23
+ * 3. Both screens show the same 6-digit code, derived from the link id and
24
+ * that key (`deriveIdentityLinkCode` in `@oxy.so/core`); the person checks
25
+ * they match, so a photographed QR cannot slip another key in.
26
+ * 4. The panel completes with the email code: the account gains the root,
27
+ * loses the email, and Commons signs in with it.
28
+ *
29
+ * The server stores only the challenge's hash; the challenge travels in the QR.
30
+ */
31
+ const zod_1 = require("zod");
32
+ const identityProof_1 = require("./identityProof");
33
+ const signIn_1 = require("./signIn");
34
+ exports.IDENTITY_LINK_STATUSES = ['pending', 'signed', 'completed', 'cancelled'];
35
+ /** The scheme and host Commons routes a link QR to. */
36
+ exports.IDENTITY_LINK_QR_PREFIX = 'oxycommons://link';
37
+ const LINK_ID = /^[0-9a-f]{32}$/;
38
+ const CHALLENGE = /^[0-9a-f]{64}$/;
39
+ exports.identityLinkIdSchema = zod_1.z.string().trim().regex(LINK_ID, 'linkId must be 32 lowercase hex characters');
40
+ /** The QR auth.oxy.so shows: the request's id and the challenge Commons signs. */
41
+ function buildIdentityLinkQrPayload(linkId, challenge) {
42
+ return `${exports.IDENTITY_LINK_QR_PREFIX}?id=${linkId}&c=${challenge}`;
43
+ }
44
+ /** The request a scanned code names, or `null` for anything that is not a link QR. */
45
+ function parseIdentityLinkQrPayload(raw) {
46
+ const value = raw.trim();
47
+ if (!value.startsWith(`${exports.IDENTITY_LINK_QR_PREFIX}?`))
48
+ return null;
49
+ // Parsed by hand: React Native's `URLSearchParams` does not implement `get`.
50
+ const params = new Map();
51
+ for (const pair of value.slice(exports.IDENTITY_LINK_QR_PREFIX.length + 1).split('&')) {
52
+ const separator = pair.indexOf('=');
53
+ if (separator > 0)
54
+ params.set(pair.slice(0, separator), pair.slice(separator + 1));
55
+ }
56
+ const linkId = params.get('id') ?? '';
57
+ const challenge = params.get('c') ?? '';
58
+ if (!LINK_ID.test(linkId) || !CHALLENGE.test(challenge))
59
+ return null;
60
+ return { linkId, challenge };
61
+ }
62
+ exports.identityLinkCreateResponseSchema = zod_1.z.object({
63
+ linkId: exports.identityLinkIdSchema,
64
+ challenge: zod_1.z.string().regex(CHALLENGE),
65
+ expiresAt: zod_1.z.number().int().positive(),
66
+ qrPayload: zod_1.z.string().startsWith(exports.IDENTITY_LINK_QR_PREFIX),
67
+ });
68
+ exports.identityLinkStateSchema = zod_1.z.object({
69
+ status: zod_1.z.enum(exports.IDENTITY_LINK_STATUSES),
70
+ userId: zod_1.z.string().min(1),
71
+ username: zod_1.z.string().nullable(),
72
+ publicKey: zod_1.z.string().nullable(),
73
+ audience: zod_1.z.string().min(1),
74
+ expiresAt: zod_1.z.number().int().positive(),
75
+ });
76
+ /** `POST /identity/link/:linkId/proof` — from Commons, no bearer. */
77
+ exports.identityLinkProofRequestSchema = zod_1.z
78
+ .object({
79
+ publicKey: zod_1.z
80
+ .string()
81
+ .trim()
82
+ .toLowerCase()
83
+ .regex(/^04[0-9a-f]{128}$/, 'publicKey must be an uncompressed secp256k1 key'),
84
+ proof: identityProof_1.identityProofSchema,
85
+ })
86
+ .strict();
87
+ /**
88
+ * `POST /identity/link/:linkId/complete` — the account's own confirmation: a
89
+ * code just sent to its email for this link (`reauth`, plus its authenticator
90
+ * code when it has one).
91
+ */
92
+ exports.identityLinkCompleteRequestSchema = zod_1.z.object({ reauth: signIn_1.emailReauthProofSchema }).strict();
@@ -32,26 +32,12 @@ exports.IDENTITY_PROOF_CHALLENGE_TTL_MS = 5 * 60 * 1000;
32
32
  * action and spent only by a proof for that action.
33
33
  */
34
34
  exports.IDENTITY_PROOF_ACTIONS = {
35
- /** A keyless account's FIRST root, stored with its web envelope. */
36
- establish: 'web_envelope_establish',
37
- /** Replace the web envelope (add or remove a wrap, re-seal). */
38
- put: 'web_envelope_put',
39
- /** Record that the recovery material is written down. */
40
- phraseConfirmed: 'web_envelope_phrase_confirmed',
41
- /** Record that the recovery material re-derived the root. */
42
- recoveryVerified: 'web_envelope_recovery_verified',
43
- /** Remove the web holder. */
44
- delete: 'web_envelope_delete',
45
- /** Link a keyless account's first root without a web envelope (`POST /auth/link`). */
35
+ /**
36
+ * Link Commons' root to an account that has none (`POST /auth/link`).
37
+ * The account becomes self-custodied and its email is deleted
38
+ * (ADR 0029 D3).
39
+ */
46
40
  link: 'link_identity',
47
- /** Create a personal account together with its root (passkey sign-up). */
48
- enroll: 'enroll_identity',
49
- /** Prove the root to start signed-out recovery. */
50
- recoverStart: 'recover_account_start',
51
- /** Bind the new passkey and envelope when completing signed-out recovery. */
52
- recoverComplete: 'recover_account_complete',
53
- /** Seal the root for the Commons device that joined a move (payload: move id + sealed bytes). */
54
- moveSeal: 'identity_move_seal',
55
41
  };
56
42
  exports.IDENTITY_PROOF_ACTION_VALUES = Object.values(exports.IDENTITY_PROOF_ACTIONS);
57
43
  const HEX_DIGEST = /^[0-9a-f]{64}$/;
@@ -144,21 +130,12 @@ exports.identityProofChallengeResponseSchema = zod_1.z.object({
144
130
  */
145
131
  exports.IDENTITY_ERROR_CODES = {
146
132
  proofInvalid: 'IDENTITY_PROOF_INVALID',
147
- revisionConflict: 'IDENTITY_ENVELOPE_REVISION_CONFLICT',
148
133
  rootAlreadyLinked: 'IDENTITY_ROOT_ALREADY_LINKED',
149
134
  rootLinkedElsewhere: 'IDENTITY_ROOT_LINKED_ELSEWHERE',
150
- noRoot: 'IDENTITY_NO_ROOT',
151
135
  freshFactorRequired: 'IDENTITY_FRESH_FACTOR_REQUIRED',
152
- lastWebHolder: 'IDENTITY_LAST_WEB_HOLDER',
153
- enrollmentRequired: 'IDENTITY_ENROLLMENT_REQUIRED',
154
- enrollmentInvalid: 'IDENTITY_ENROLLMENT_INVALID',
155
136
  notPersonal: 'IDENTITY_NOT_PERSONAL_ACCOUNT',
156
- recoveryFailed: 'IDENTITY_RECOVERY_FAILED',
157
137
  };
158
138
  exports.identityRootStatusSchema = zod_1.z.object({
159
139
  rootLinked: zod_1.z.boolean(),
160
- webHolder: zod_1.z.object({ passkeys: zod_1.z.number().int().nonnegative(), verifiedPasskeys: zod_1.z.number().int().nonnegative() }).nullable(),
161
- hasPhrase: zod_1.z.boolean().nullable(),
162
- phraseConfirmedAt: zod_1.z.string().datetime().nullable(),
163
- recoveryVerifiedAt: zod_1.z.string().datetime().nullable(),
140
+ recoveryEmail: zod_1.z.string().nullable(),
164
141
  });