@oxy.so/contracts 4.15.0 → 4.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/accountEmail.js +12 -34
- package/dist/cjs/accountGraph.js +22 -320
- package/dist/cjs/agentKeys.js +11 -13
- package/dist/cjs/client/accountEmail.js +48 -0
- package/dist/cjs/client/accountGraph.js +323 -0
- package/dist/cjs/client/agentKeys.js +77 -0
- package/dist/cjs/client/check.js +466 -0
- package/dist/cjs/client/deviceBoot.js +22 -0
- package/dist/cjs/client/deviceDirectory.js +51 -0
- package/dist/cjs/client/deviceSession.js +63 -0
- package/dist/cjs/client/externalIdentity.js +19 -0
- package/dist/cjs/client/identity.js +45 -0
- package/dist/cjs/client/identityLink.js +55 -0
- package/dist/cjs/client/identityProof.js +109 -0
- package/dist/cjs/client/index.js +190 -0
- package/dist/cjs/client/organizations.js +114 -0
- package/dist/cjs/client/products.js +153 -0
- package/dist/cjs/client/recommendations.js +33 -0
- package/dist/cjs/client/reputation.js +41 -0
- package/dist/cjs/client/safety.js +15 -0
- package/dist/cjs/client/sessionStatus.js +23 -0
- package/dist/cjs/client/signIn.js +157 -0
- package/dist/cjs/client/userResponse.js +15 -0
- package/dist/cjs/client/username.js +173 -0
- package/dist/cjs/deviceSession.js +3 -16
- package/dist/cjs/identityLink.js +13 -34
- package/dist/cjs/identityProof.js +18 -106
- package/dist/cjs/index.js +15 -10
- package/dist/cjs/inference/money.js +10 -0
- package/dist/cjs/inference/version.js +1 -1
- package/dist/cjs/organizations.js +21 -41
- package/dist/cjs/products/billing.js +0 -1
- package/dist/cjs/products/checkout.js +1 -14
- package/dist/cjs/products/plans.js +2 -18
- package/dist/cjs/reputation.js +3 -34
- package/dist/cjs/signIn.js +57 -96
- package/dist/cjs/userResponse.js +1 -8
- package/dist/cjs/username.js +23 -158
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/accountEmail.js +4 -32
- package/dist/esm/accountGraph.js +4 -309
- package/dist/esm/agentKeys.js +4 -10
- package/dist/esm/client/accountEmail.js +45 -0
- package/dist/esm/client/accountGraph.js +314 -0
- package/dist/esm/client/agentKeys.js +74 -0
- package/dist/esm/client/check.js +428 -0
- package/dist/esm/client/deviceBoot.js +19 -0
- package/dist/esm/client/deviceDirectory.js +48 -0
- package/dist/esm/client/deviceSession.js +60 -0
- package/dist/esm/client/externalIdentity.js +16 -0
- package/dist/esm/client/identity.js +42 -0
- package/dist/esm/client/identityLink.js +50 -0
- package/dist/esm/client/identityProof.js +104 -0
- package/dist/esm/client/index.js +37 -0
- package/dist/esm/client/organizations.js +111 -0
- package/dist/esm/client/products.js +150 -0
- package/dist/esm/client/recommendations.js +30 -0
- package/dist/esm/client/reputation.js +37 -0
- package/dist/esm/client/safety.js +12 -0
- package/dist/esm/client/sessionStatus.js +20 -0
- package/dist/esm/client/signIn.js +151 -0
- package/dist/esm/client/userResponse.js +12 -0
- package/dist/esm/client/username.js +167 -0
- package/dist/esm/deviceSession.js +1 -15
- package/dist/esm/identityLink.js +4 -27
- package/dist/esm/identityProof.js +4 -99
- package/dist/esm/index.js +2 -2
- package/dist/esm/inference/money.js +10 -0
- package/dist/esm/inference/version.js +1 -1
- package/dist/esm/organizations.js +4 -31
- package/dist/esm/products/billing.js +0 -1
- package/dist/esm/products/checkout.js +0 -13
- package/dist/esm/products/plans.js +1 -17
- package/dist/esm/reputation.js +1 -32
- package/dist/esm/signIn.js +29 -83
- package/dist/esm/userResponse.js +1 -8
- package/dist/esm/username.js +4 -148
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/accountEmail.d.ts +2 -34
- package/dist/types/accountGraph.d.ts +12 -208
- package/dist/types/agency.d.ts +24 -24
- package/dist/types/agentKeys.d.ts +8 -6
- package/dist/types/client/accountEmail.d.ts +44 -0
- package/dist/types/client/accountGraph.d.ts +203 -0
- package/dist/types/client/agentKeys.d.ts +113 -0
- package/dist/types/client/check.d.ts +184 -0
- package/dist/types/client/deviceBoot.d.ts +6 -0
- package/dist/types/client/deviceDirectory.d.ts +134 -0
- package/dist/types/client/deviceSession.d.ts +91 -0
- package/dist/types/client/externalIdentity.d.ts +14 -0
- package/dist/types/client/identity.d.ts +8 -0
- package/dist/types/client/identityLink.d.ts +21 -0
- package/dist/types/client/identityProof.d.ts +73 -0
- package/dist/types/client/index.d.ts +37 -0
- package/dist/types/client/organizations.d.ts +167 -0
- package/dist/types/client/products.d.ts +145 -0
- package/dist/types/client/recommendations.d.ts +27 -0
- package/dist/types/client/reputation.d.ts +28 -0
- package/dist/types/client/safety.d.ts +13 -0
- package/dist/types/client/sessionStatus.d.ts +19 -0
- package/dist/types/client/signIn.d.ts +107 -0
- package/dist/types/client/userResponse.d.ts +7 -0
- package/dist/types/client/username.d.ts +126 -0
- package/dist/types/deviceDirectory.d.ts +28 -28
- package/dist/types/deviceSession.d.ts +1 -12
- package/dist/types/externalIdentity.d.ts +11 -44
- package/dist/types/identityLink.d.ts +3 -11
- package/dist/types/identityProof.d.ts +2 -65
- package/dist/types/index.d.ts +3 -3
- package/dist/types/inference/catalogue.d.ts +7 -7
- package/dist/types/inference/decisions.d.ts +41 -41
- package/dist/types/inference/economics.d.ts +5 -5
- package/dist/types/inference/embeddings.d.ts +16 -16
- package/dist/types/inference/errors.d.ts +8 -8
- package/dist/types/inference/inbox.d.ts +8 -8
- package/dist/types/inference/money.d.ts +17 -8
- package/dist/types/inference/priceVersion.d.ts +12 -12
- package/dist/types/inference/privateAutoExecution.d.ts +6 -6
- package/dist/types/inference/realtime.d.ts +54 -54
- package/dist/types/inference/request.d.ts +28 -28
- package/dist/types/inference/routingPolicy.d.ts +7 -7
- package/dist/types/inference/streamEvents.d.ts +26 -26
- package/dist/types/inference/usage.d.ts +32 -32
- package/dist/types/inference/version.d.ts +1 -1
- package/dist/types/oauth.d.ts +2 -2
- package/dist/types/organizations.d.ts +8 -10
- package/dist/types/products/billing.d.ts +3 -8
- package/dist/types/products/checkout.d.ts +0 -36
- package/dist/types/products/plans.d.ts +5 -102
- package/dist/types/reputation.d.ts +1 -11
- package/dist/types/sessionStatus.d.ts +2 -2
- package/dist/types/signIn.d.ts +74 -76
- package/dist/types/userResponse.d.ts +23 -159
- package/dist/types/username.d.ts +1 -96
- package/package.json +20 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-response checks, without Zod. Schemas: `../userResponse.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Part of `@oxy.so/contracts/client`: never import `zod` here (see `./check.ts`).
|
|
5
|
+
*/
|
|
6
|
+
import { exact, looseObject, optional, string } from './check.js';
|
|
7
|
+
export const userNameCheck = exact()(looseObject({
|
|
8
|
+
first: optional(string()),
|
|
9
|
+
last: optional(string()),
|
|
10
|
+
full: optional(string()),
|
|
11
|
+
displayName: optional(string()),
|
|
12
|
+
}));
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The username policy, without Zod. Schemas: `../username.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Part of `@oxy.so/contracts/client`: never import `zod` here (see `./check.ts`).
|
|
5
|
+
*/
|
|
6
|
+
import { maxLength, minLength, pattern, refine, string, trim } from './check.js';
|
|
7
|
+
/** Shortest storable handle. `oxy` sets the floor. */
|
|
8
|
+
export const USERNAME_MIN_LENGTH = 3;
|
|
9
|
+
/** Longest storable handle, and the `maxLength` an input field should carry. */
|
|
10
|
+
export const USERNAME_MAX_LENGTH = 30;
|
|
11
|
+
/** The 400 / inline-validation copy for every path that rejects a handle. */
|
|
12
|
+
export const USERNAME_INVALID_MESSAGE = 'Username must be 3-30 characters, use only letters, numbers, hyphens and underscores, ' +
|
|
13
|
+
'start and end with a letter or number, and never repeat a separator';
|
|
14
|
+
/**
|
|
15
|
+
* Exact-match handles nobody may newly claim, regardless of account kind.
|
|
16
|
+
*
|
|
17
|
+
* This is NOT a namespace tightening like the bot suffix — it is a list of
|
|
18
|
+
* names withheld from `users_lower_username_key` before anybody asks for
|
|
19
|
+
* them. Compared against the same normalization the unique index applies
|
|
20
|
+
* (`lower(btrim(username))`), so `Admin`, ` ADMIN ` and `admin` are the one
|
|
21
|
+
* name this list means.
|
|
22
|
+
*
|
|
23
|
+
* `oxy`, `mention`, `homiio`, `clarity`, `faircoin`, `astro` and `mercaria`
|
|
24
|
+
* are deliberately NOT here: each already has an `organization`/`project`
|
|
25
|
+
* row, so the unique index already refuses a second one, and `oxy` is the
|
|
26
|
+
* name `USERNAME_MIN_LENGTH` is pinned against in
|
|
27
|
+
* `__tests__/username.test.ts` — listing it would make
|
|
28
|
+
* `usernameSchema.safeParse('oxy')` fail and falsify that comment. A brand
|
|
29
|
+
* with no account of its own yet (measured 2026-09-18: Alia, Allo, TNP,
|
|
30
|
+
* Kaana, Bloom) has no row to protect it, so it is listed until one exists.
|
|
31
|
+
*/
|
|
32
|
+
export const RESERVED_USERNAMES = new Set([
|
|
33
|
+
// Oxy product lines with no account row of their own yet.
|
|
34
|
+
'alia',
|
|
35
|
+
'allo',
|
|
36
|
+
'tnp',
|
|
37
|
+
'kaana',
|
|
38
|
+
'bloom',
|
|
39
|
+
// System / role words a signup impersonating staff or Oxy itself would reach for.
|
|
40
|
+
'admin',
|
|
41
|
+
'administrator',
|
|
42
|
+
'root',
|
|
43
|
+
'superadmin',
|
|
44
|
+
'super',
|
|
45
|
+
'superuser',
|
|
46
|
+
'support',
|
|
47
|
+
'staff',
|
|
48
|
+
'moderator',
|
|
49
|
+
'mod',
|
|
50
|
+
'security',
|
|
51
|
+
'system',
|
|
52
|
+
'official',
|
|
53
|
+
'help',
|
|
54
|
+
'noreply',
|
|
55
|
+
'anonymous',
|
|
56
|
+
'everyone',
|
|
57
|
+
'owner',
|
|
58
|
+
]);
|
|
59
|
+
/** The 400 / inline-validation copy for a handle on {@link RESERVED_USERNAMES}. */
|
|
60
|
+
export const RESERVED_USERNAME_MESSAGE = 'This username is reserved and cannot be registered';
|
|
61
|
+
/**
|
|
62
|
+
* {@link RESERVED_USERNAMES}, minus `alia`, checked against each `-`/`_`
|
|
63
|
+
* separated SEGMENT of a candidate rather than the whole string — so
|
|
64
|
+
* `official-oxy`, `super-admin` and `team-kaana` are refused the same as the
|
|
65
|
+
* bare words, without banning every word that merely CONTAINS one as a
|
|
66
|
+
* substring (`superman`, `modern`, `grassroot`, `homeowner` all stay legal:
|
|
67
|
+
* none of them separates the reserved word from the rest with `-` or `_`).
|
|
68
|
+
*
|
|
69
|
+
* `alia` is excluded because `alia-` is the live internal-cost-centre
|
|
70
|
+
* namespace: `alia-production-chat` is a minted account and
|
|
71
|
+
* `alia-research` / `alia-voice` / `alia-evaluations` are pinned as legal
|
|
72
|
+
* slugs in `__tests__/username.test.ts` and `internalCostCenterSpecs.test.ts`.
|
|
73
|
+
* Segment-matching `alia` would refuse all four. The bare word `alia` is
|
|
74
|
+
* still refused — {@link RESERVED_USERNAMES} above catches it exactly.
|
|
75
|
+
*/
|
|
76
|
+
export const RESERVED_USERNAME_SEGMENTS = new Set([...RESERVED_USERNAMES].filter((word) => word !== 'alia'));
|
|
77
|
+
/** The 400 / inline-validation copy for a handle that is only digits. */
|
|
78
|
+
export const NUMERIC_USERNAME_MESSAGE = 'Username cannot be only numbers';
|
|
79
|
+
/**
|
|
80
|
+
* Alphanumeric runs joined by single separators, as a SOURCE string.
|
|
81
|
+
*
|
|
82
|
+
* A string rather than a literal because the OpenAPI docblocks that publish this
|
|
83
|
+
* rule (`POST /auth/register`, `PUT /users/:userId`) must quote it verbatim, and
|
|
84
|
+
* `usernamePolicySingleSource.test.ts` compares them against THIS constant. A
|
|
85
|
+
* published `pattern:` that drifts from the enforced rule is a lie told to every
|
|
86
|
+
* client that generates from the spec, and it is exactly the kind of copy nobody
|
|
87
|
+
* notices going stale.
|
|
88
|
+
*
|
|
89
|
+
* Deliberately NOT re-exported from the package barrel: it exists for the
|
|
90
|
+
* schema below and for that one gate. Anything validating a username uses
|
|
91
|
+
* {@link usernameSchema}, so there is no second way to ask the question.
|
|
92
|
+
*
|
|
93
|
+
* Written as an unambiguous alternation rather than a lookahead: every character
|
|
94
|
+
* belongs to exactly one branch, so matching is linear and there is no
|
|
95
|
+
* backtracking to bound. It also carries no `\p{…}` property escape, which
|
|
96
|
+
* mobile Hermes throws on at runtime — this module is reachable from every React
|
|
97
|
+
* Native consumer.
|
|
98
|
+
*/
|
|
99
|
+
export const USERNAME_PATTERN_SOURCE = '^[A-Za-z0-9]+(?:[-_][A-Za-z0-9]+)*$';
|
|
100
|
+
export const USERNAME_PATTERN = new RegExp(USERNAME_PATTERN_SOURCE);
|
|
101
|
+
/**
|
|
102
|
+
* Drop the characters the policy forbids, for an input field that filters
|
|
103
|
+
* keystrokes.
|
|
104
|
+
*
|
|
105
|
+
* This is a TYPING aid and nothing else — the result still has to pass
|
|
106
|
+
* {@link usernameSchema}, which is what decides. It does not lower-case (case is
|
|
107
|
+
* preserved, see the header) and it cannot repair a name: a value that is too
|
|
108
|
+
* short, edge-separated or doubly-separated comes back unchanged and fails
|
|
109
|
+
* validation with a message, which is the outcome the coercing rules this
|
|
110
|
+
* replaces used to hide.
|
|
111
|
+
*/
|
|
112
|
+
export function stripDisallowedUsernameCharacters(input) {
|
|
113
|
+
return input.replace(/[^A-Za-z0-9_-]/g, '');
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The label a bot's handle ends in — the ONE exception the policy carries.
|
|
117
|
+
*
|
|
118
|
+
* Lower-case here because it is what a generator appends; the comparison that
|
|
119
|
+
* enforces it folds case, so `MyBot` satisfies it just as `mybot` does.
|
|
120
|
+
*
|
|
121
|
+
* Deliberately NOT re-exported from the package barrel, for the same reason
|
|
122
|
+
* {@link USERNAME_PATTERN_SOURCE} is not: it exists for the schema and the
|
|
123
|
+
* generator aid below. A consumer that wants to SAY the rule uses
|
|
124
|
+
* {@link BOT_USERNAME_INVALID_MESSAGE}, which already quotes it, and one that
|
|
125
|
+
* wants to APPLY it uses {@link applyBotUsernameSuffix} — so there is no second
|
|
126
|
+
* way to spell the check.
|
|
127
|
+
*/
|
|
128
|
+
export const BOT_USERNAME_SUFFIX = 'bot';
|
|
129
|
+
/** The 400 / inline-validation copy for a bot handle that carries no label. */
|
|
130
|
+
export const BOT_USERNAME_INVALID_MESSAGE = `A bot account's username must end in "${BOT_USERNAME_SUFFIX}"`;
|
|
131
|
+
/**
|
|
132
|
+
* Label a proposed bot handle, so a generator PROPOSES something the server will
|
|
133
|
+
* accept.
|
|
134
|
+
*
|
|
135
|
+
* Alia builds an agent's handle from its display name; without this it would
|
|
136
|
+
* propose `garden-helper` for a bot and be 400ed on submit, which is the same
|
|
137
|
+
* "suggest, then refuse" defect the minimum length already caused once.
|
|
138
|
+
*
|
|
139
|
+
* It appends and never inserts: a separator is the caller's choice, so
|
|
140
|
+
* `garden-helper` becomes `garden-helperbot` while a caller who typed
|
|
141
|
+
* `garden-helper-` gets `garden-helper-bot`. It truncates to leave room rather
|
|
142
|
+
* than overflowing {@link USERNAME_MAX_LENGTH}.
|
|
143
|
+
*
|
|
144
|
+
* Like {@link stripDisallowedUsernameCharacters} it is an aid, not a decision: it
|
|
145
|
+
* cannot repair a handle the base policy refuses, and the result still has to
|
|
146
|
+
* pass {@link botUsernameSchema}.
|
|
147
|
+
*/
|
|
148
|
+
export function applyBotUsernameSuffix(candidate) {
|
|
149
|
+
const trimmed = candidate.trim();
|
|
150
|
+
if (trimmed.toLowerCase().endsWith(BOT_USERNAME_SUFFIX)) {
|
|
151
|
+
return trimmed;
|
|
152
|
+
}
|
|
153
|
+
return trimmed.slice(0, USERNAME_MAX_LENGTH - BOT_USERNAME_SUFFIX.length) + BOT_USERNAME_SUFFIX;
|
|
154
|
+
}
|
|
155
|
+
/* -------------------------------------------------------------------------- */
|
|
156
|
+
/* Checks */
|
|
157
|
+
/* -------------------------------------------------------------------------- */
|
|
158
|
+
/** The username policy as a check — the twin of `usernameSchema`, same messages. */
|
|
159
|
+
export const usernameCheck = refine(refine(refine(string(trim, minLength(USERNAME_MIN_LENGTH, USERNAME_INVALID_MESSAGE), maxLength(USERNAME_MAX_LENGTH, USERNAME_INVALID_MESSAGE), pattern(USERNAME_PATTERN, USERNAME_INVALID_MESSAGE)), (username) => !/^[0-9]+$/.test(username), NUMERIC_USERNAME_MESSAGE), (username) => !RESERVED_USERNAMES.has(username.toLowerCase()), RESERVED_USERNAME_MESSAGE), (username) => !username
|
|
160
|
+
.split(/[-_]/)
|
|
161
|
+
.some((segment) => RESERVED_USERNAME_SEGMENTS.has(segment.toLowerCase())), RESERVED_USERNAME_MESSAGE);
|
|
162
|
+
/** A bot's username: the policy plus the `bot` suffix — the twin of `botUsernameSchema`. */
|
|
163
|
+
export const botUsernameCheck = refine(usernameCheck, (username) => username.toLowerCase().endsWith(BOT_USERNAME_SUFFIX), BOT_USERNAME_INVALID_MESSAGE);
|
|
164
|
+
/** The twin of `usernameSchemaForAccountKind`. */
|
|
165
|
+
export function usernameCheckForAccountKind(kind) {
|
|
166
|
+
return kind === 'bot' ? botUsernameCheck : usernameCheck;
|
|
167
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
export { SESSION_ACCOUNTS_CHANGED_EVENT } from './client/deviceSession.js';
|
|
2
3
|
export const sessionAccountSchema = z.object({
|
|
3
4
|
accountId: z.string(),
|
|
4
5
|
sessionId: z.string(),
|
|
@@ -58,21 +59,6 @@ export const deviceTokenMintResponseSchema = z.object({
|
|
|
58
59
|
nextDeviceSecret: z.string(),
|
|
59
60
|
state: deviceSessionStateSchema,
|
|
60
61
|
});
|
|
61
|
-
/* -------------------------------------------------------------------------- */
|
|
62
|
-
/* Instant cross-app session sync (token-free socket signal) */
|
|
63
|
-
/* -------------------------------------------------------------------------- */
|
|
64
|
-
/**
|
|
65
|
-
* Name of the token-free Socket.IO event emitted to room `user:<userId>` on
|
|
66
|
-
* every DeviceSession mutation that changes what is signed in for that user.
|
|
67
|
-
*
|
|
68
|
-
* This is a pure SIGNAL — it carries NO access token, NO deviceSecret and NO
|
|
69
|
-
* account bodies. A client that receives it re-fetches its authenticated
|
|
70
|
-
* session/account state (`GET /session/device/state`, `GET /accounts`). Unlike
|
|
71
|
-
* `session_state` (scoped to `device:<deviceId>`, i.e. a single origin), this
|
|
72
|
-
* reaches ALL of a user's connected sockets across their devices/origins so
|
|
73
|
-
* every Oxy app reflects an add / switch / signout instantly.
|
|
74
|
-
*/
|
|
75
|
-
export const SESSION_ACCOUNTS_CHANGED_EVENT = 'session_accounts_changed';
|
|
76
62
|
/**
|
|
77
63
|
* Why the signed-in set changed:
|
|
78
64
|
* - `login` — a brand-new session was minted for the user (QR / cross-app authorize)
|
package/dist/esm/identityLink.js
CHANGED
|
@@ -26,37 +26,14 @@
|
|
|
26
26
|
import { z } from 'zod';
|
|
27
27
|
import { identityProofSchema } from './identityProof.js';
|
|
28
28
|
import { emailReauthProofSchema } from './signIn.js';
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const CHALLENGE = /^[0-9a-f]{64}$/;
|
|
29
|
+
// The Zod-free half of this contract lives in `./client/` (`@oxy.so/contracts/client`);
|
|
30
|
+
// it is re-exported here so the root entry is unchanged.
|
|
31
|
+
import { IDENTITY_LINK_STATUSES, IDENTITY_LINK_QR_PREFIX, LINK_ID, CHALLENGE, } from './client/identityLink.js';
|
|
32
|
+
export { IDENTITY_LINK_STATUSES, IDENTITY_LINK_QR_PREFIX, buildIdentityLinkQrPayload, parseIdentityLinkQrPayload, } from './client/identityLink.js';
|
|
34
33
|
export const identityLinkIdSchema = z
|
|
35
34
|
.string()
|
|
36
35
|
.trim()
|
|
37
36
|
.regex(LINK_ID, 'linkId must be 32 lowercase hex characters');
|
|
38
|
-
/** The QR auth.oxy.so shows: the request's id and the challenge Commons signs. */
|
|
39
|
-
export function buildIdentityLinkQrPayload(linkId, challenge) {
|
|
40
|
-
return `${IDENTITY_LINK_QR_PREFIX}?id=${linkId}&c=${challenge}`;
|
|
41
|
-
}
|
|
42
|
-
/** The request a scanned code names, or `null` for anything that is not a link QR. */
|
|
43
|
-
export function parseIdentityLinkQrPayload(raw) {
|
|
44
|
-
const value = raw.trim();
|
|
45
|
-
if (!value.startsWith(`${IDENTITY_LINK_QR_PREFIX}?`))
|
|
46
|
-
return null;
|
|
47
|
-
// Parsed by hand: React Native's `URLSearchParams` does not implement `get`.
|
|
48
|
-
const params = new Map();
|
|
49
|
-
for (const pair of value.slice(IDENTITY_LINK_QR_PREFIX.length + 1).split('&')) {
|
|
50
|
-
const separator = pair.indexOf('=');
|
|
51
|
-
if (separator > 0)
|
|
52
|
-
params.set(pair.slice(0, separator), pair.slice(separator + 1));
|
|
53
|
-
}
|
|
54
|
-
const linkId = params.get('id') ?? '';
|
|
55
|
-
const challenge = params.get('c') ?? '';
|
|
56
|
-
if (!LINK_ID.test(linkId) || !CHALLENGE.test(challenge))
|
|
57
|
-
return null;
|
|
58
|
-
return { linkId, challenge };
|
|
59
|
-
}
|
|
60
37
|
export const identityLinkCreateResponseSchema = z.object({
|
|
61
38
|
linkId: identityLinkIdSchema,
|
|
62
39
|
challenge: z.string().regex(CHALLENGE),
|
|
@@ -16,94 +16,10 @@
|
|
|
16
16
|
* primitive and passes the hex digest in).
|
|
17
17
|
*/
|
|
18
18
|
import { z } from 'zod';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export
|
|
23
|
-
/** How long a proof challenge lives: one interactive ceremony. */
|
|
24
|
-
export const IDENTITY_PROOF_CHALLENGE_TTL_MS = 5 * 60 * 1000;
|
|
25
|
-
/**
|
|
26
|
-
* Everything a root proof may authorize. A challenge is minted for exactly one
|
|
27
|
-
* action and spent only by a proof for that action.
|
|
28
|
-
*/
|
|
29
|
-
export const IDENTITY_PROOF_ACTIONS = {
|
|
30
|
-
/**
|
|
31
|
-
* Link Commons' root to an account that has none (`POST /auth/link`).
|
|
32
|
-
* The account becomes self-custodied and its email is deleted
|
|
33
|
-
* (ADR 0029 D3).
|
|
34
|
-
*/
|
|
35
|
-
link: 'link_identity',
|
|
36
|
-
};
|
|
37
|
-
export const IDENTITY_PROOF_ACTION_VALUES = Object.values(IDENTITY_PROOF_ACTIONS);
|
|
38
|
-
const HEX_DIGEST = /^[0-9a-f]{64}$/;
|
|
39
|
-
const ROOT_KEY = /^04[0-9a-f]{128}$/;
|
|
40
|
-
const CHALLENGE = /^[0-9a-f]{64}$/;
|
|
41
|
-
/**
|
|
42
|
-
* Canonical JSON: object keys sorted by UTF-16 code unit, no whitespace,
|
|
43
|
-
* `undefined` members omitted, arrays in order. Numbers must be finite. This is
|
|
44
|
-
* the ONLY serializer for anything digested into a proof.
|
|
45
|
-
*/
|
|
46
|
-
export function canonicalJson(value) {
|
|
47
|
-
if (value === null)
|
|
48
|
-
return 'null';
|
|
49
|
-
switch (typeof value) {
|
|
50
|
-
case 'string':
|
|
51
|
-
case 'boolean':
|
|
52
|
-
return JSON.stringify(value);
|
|
53
|
-
case 'number':
|
|
54
|
-
if (!Number.isFinite(value))
|
|
55
|
-
throw new Error('canonicalJson: non-finite number');
|
|
56
|
-
return JSON.stringify(value);
|
|
57
|
-
case 'object': {
|
|
58
|
-
if (Array.isArray(value)) {
|
|
59
|
-
return `[${value.map((entry) => (entry === undefined ? 'null' : canonicalJson(entry))).join(',')}]`;
|
|
60
|
-
}
|
|
61
|
-
const record = value;
|
|
62
|
-
const keys = Object.keys(record)
|
|
63
|
-
.filter((key) => record[key] !== undefined)
|
|
64
|
-
.sort();
|
|
65
|
-
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`;
|
|
66
|
-
}
|
|
67
|
-
default:
|
|
68
|
-
throw new Error(`canonicalJson: unsupported ${typeof value}`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* The exact bytes a root signs. Throws on a malformed claim rather than signing
|
|
73
|
-
* (or verifying) something ambiguous.
|
|
74
|
-
*/
|
|
75
|
-
export function buildIdentityProofMessage(claims) {
|
|
76
|
-
if (!IDENTITY_PROOF_ACTION_VALUES.includes(claims.action))
|
|
77
|
-
throw new Error('identity proof: unknown action');
|
|
78
|
-
if (!claims.subject || !claims.actor)
|
|
79
|
-
throw new Error('identity proof: subject and actor are required');
|
|
80
|
-
if (!ROOT_KEY.test(claims.rootPublicKey))
|
|
81
|
-
throw new Error('identity proof: rootPublicKey must be canonical');
|
|
82
|
-
if (claims.payloadDigest !== null && !HEX_DIGEST.test(claims.payloadDigest)) {
|
|
83
|
-
throw new Error('identity proof: payloadDigest must be a lowercase SHA-256 hex digest');
|
|
84
|
-
}
|
|
85
|
-
if (claims.expectedRevision !== null &&
|
|
86
|
-
(!Number.isSafeInteger(claims.expectedRevision) || claims.expectedRevision < 0)) {
|
|
87
|
-
throw new Error('identity proof: expectedRevision must be a non-negative integer');
|
|
88
|
-
}
|
|
89
|
-
if (!CHALLENGE.test(claims.challenge))
|
|
90
|
-
throw new Error('identity proof: challenge must be 64 lowercase hex characters');
|
|
91
|
-
if (!Number.isSafeInteger(claims.expiresAt) || claims.expiresAt <= 0)
|
|
92
|
-
throw new Error('identity proof: expiresAt must be unix milliseconds');
|
|
93
|
-
return canonicalJson({
|
|
94
|
-
v: IDENTITY_PROOF_VERSION,
|
|
95
|
-
domain: IDENTITY_PROOF_DOMAIN,
|
|
96
|
-
action: claims.action,
|
|
97
|
-
subject: claims.subject,
|
|
98
|
-
actor: claims.actor,
|
|
99
|
-
rootPublicKey: claims.rootPublicKey,
|
|
100
|
-
payloadDigest: claims.payloadDigest,
|
|
101
|
-
expectedRevision: claims.expectedRevision,
|
|
102
|
-
audience: claims.audience,
|
|
103
|
-
challenge: claims.challenge,
|
|
104
|
-
expiresAt: claims.expiresAt,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
19
|
+
// The Zod-free half of this contract lives in `./client/` (`@oxy.so/contracts/client`);
|
|
20
|
+
// it is re-exported here so the root entry is unchanged.
|
|
21
|
+
import { IDENTITY_PROOF_VERSION, IDENTITY_PROOF_ACTION_VALUES, CHALLENGE, } from './client/identityProof.js';
|
|
22
|
+
export { IDENTITY_PROOF_VERSION, IDENTITY_PROOF_DOMAIN, IDENTITY_PROOF_AUDIENCE, IDENTITY_PROOF_CHALLENGE_TTL_MS, IDENTITY_PROOF_ACTIONS, IDENTITY_PROOF_ACTION_VALUES, canonicalJson, buildIdentityProofMessage, IDENTITY_ERROR_CODES, } from './client/identityProof.js';
|
|
107
23
|
/** The proof as it travels: the signature plus the two claims the verifier cannot derive. */
|
|
108
24
|
export const identityProofSchema = z.object({
|
|
109
25
|
v: z.literal(IDENTITY_PROOF_VERSION),
|
|
@@ -120,17 +36,6 @@ export const identityProofChallengeResponseSchema = z.object({
|
|
|
120
36
|
expiresAt: z.number().int().positive(),
|
|
121
37
|
audience: z.string().min(1),
|
|
122
38
|
});
|
|
123
|
-
/**
|
|
124
|
-
* Stable error codes the root routes answer with (`error.code` in the API error
|
|
125
|
-
* body). Clients map these through their localization, never the English message.
|
|
126
|
-
*/
|
|
127
|
-
export const IDENTITY_ERROR_CODES = {
|
|
128
|
-
proofInvalid: 'IDENTITY_PROOF_INVALID',
|
|
129
|
-
rootAlreadyLinked: 'IDENTITY_ROOT_ALREADY_LINKED',
|
|
130
|
-
rootLinkedElsewhere: 'IDENTITY_ROOT_LINKED_ELSEWHERE',
|
|
131
|
-
freshFactorRequired: 'IDENTITY_FRESH_FACTOR_REQUIRED',
|
|
132
|
-
notPersonal: 'IDENTITY_NOT_PERSONAL_ACCOUNT',
|
|
133
|
-
};
|
|
134
39
|
export const identityRootStatusSchema = z.object({
|
|
135
40
|
rootLinked: z.boolean(),
|
|
136
41
|
recoveryEmail: z.string().nullable(),
|
package/dist/esm/index.js
CHANGED
|
@@ -100,7 +100,7 @@ export {
|
|
|
100
100
|
EMAIL_VERIFICATION_PURPOSES, EMAIL_CODE_LENGTH, EMAIL_CODE_TTL_MS, EMAIL_CODE_MAX_ATTEMPTS, EMAIL_TICKET_TTL_MS, EMAIL_VERIFICATION_ERROR_CODES, emailAddressSchema, emailTicketSchema, emailVerificationStartRequestSchema, emailVerificationStartResponseSchema, emailVerificationConfirmRequestSchema, emailVerificationConfirmResponseSchema, } from './accountEmail.js';
|
|
101
101
|
export {
|
|
102
102
|
// Email code/link, password and authenticator sign-in
|
|
103
|
-
EMAIL_SIGNIN_LINK_TTL_MS, SIGNIN_SECOND_FACTOR_TTL_MS, SIGNIN_SECOND_FACTOR_MAX_ATTEMPTS, PASSWORD_MIN_LENGTH, PASSWORD_MAX_LENGTH, TOTP_DIGITS, TOTP_PERIOD_SECONDS, TOTP_BACKUP_CODE_COUNT, SIGN_IN_ERROR_CODES, signInIdentifierSchema, passwordInputSchema, newPasswordSchema, secondFactorCodeSchema, emailSignInStartRequestSchema, emailSignInStartResponseSchema, emailSignInConfirmRequestSchema, emailSignInCodeSchema, normalizeEmailSignInCode, EMAIL_SIGNIN_LONG_CODE_ALPHABET, EMAIL_SIGNIN_LONG_CODE_LENGTH, emailSignInCollectRequestSchema, emailSignInLinkRequestSchema, emailSignInLinkResponseSchema, emailSignInPendingSchema, passwordSignInRequestSchema, secondFactorSignInRequestSchema, secondFactorRequiredSchema, signUpRequestSchema, isSecondFactorRequired, reauthProofSchema, emailReauthProofSchema, REAUTH_ACTIONS, reauthEmailStartRequestSchema, passwordSetRequestSchema, signInMethodsSchema, totpEnrollResponseSchema, totpConfirmRequestSchema, totpReauthRequestSchema, totpBackupCodesResponseSchema, } from './signIn.js';
|
|
103
|
+
EMAIL_SIGNIN_LINK_TTL_MS, SIGNIN_SECOND_FACTOR_TTL_MS, SIGNIN_SECOND_FACTOR_MAX_ATTEMPTS, PASSWORD_MIN_LENGTH, PASSWORD_MAX_LENGTH, TOTP_DIGITS, TOTP_PERIOD_SECONDS, TOTP_BACKUP_CODE_COUNT, SIGN_IN_ERROR_CODES, signInIdentifierSchema, passwordInputSchema, newPasswordSchema, secondFactorCodeSchema, emailSignInStartRequestSchema, emailSignInStartResponseSchema, emailSignInConfirmRequestSchema, emailSignInCodeSchema, normalizeEmailSignInCode, EMAIL_SIGNIN_LONG_CODE_ALPHABET, EMAIL_SIGNIN_LONG_CODE_LENGTH, emailSignInCollectRequestSchema, emailSignInLinkRequestSchema, emailSignInLinkResponseSchema, emailSignInPendingSchema, EMAIL_SIGNIN_CHOICE_TTL_MS, EMAIL_ACCOUNTS_MAX, emailSignInAccountOptionSchema, emailSignInChooseAccountSchema, emailSignInChooseRequestSchema, isEmailSignInChooseAccount, passwordSignInRequestSchema, secondFactorSignInRequestSchema, secondFactorRequiredSchema, signUpRequestSchema, isSecondFactorRequired, reauthProofSchema, emailReauthProofSchema, REAUTH_ACTIONS, reauthEmailStartRequestSchema, passwordSetRequestSchema, signInMethodsSchema, totpEnrollResponseSchema, totpConfirmRequestSchema, totpReauthRequestSchema, totpBackupCodesResponseSchema, } from './signIn.js';
|
|
104
104
|
export {
|
|
105
105
|
// Linking Commons to an account without a key (ADR 0029 D3)
|
|
106
106
|
IDENTITY_LINK_STATUSES, IDENTITY_LINK_QR_PREFIX, identityLinkIdSchema, buildIdentityLinkQrPayload, parseIdentityLinkQrPayload, identityLinkCreateResponseSchema, identityLinkStateSchema, identityLinkProofRequestSchema, identityLinkCompleteRequestSchema, } from './identityLink.js';
|
|
@@ -200,7 +200,7 @@ export { meteredGenerationSchema } from './inference/economics.js';
|
|
|
200
200
|
export { canonicalCapabilityJson, inputSatisfiesCapabilityLimits, isLoopbackOrigin, } from './capabilityBindings.js';
|
|
201
201
|
export * from './agentKeys.js';
|
|
202
202
|
export { productSubscriptionSummarySchema, productSubscriptionsResponseSchema, subscriptionCreditGrantSchema, subscriptionCreditGrantsResponseSchema, cancelProductSubscriptionSchema, productSubscriptionCancellationResultSchema, } from './products/billing.js';
|
|
203
|
-
export { personalPlanDisplayPriceSchema, personalPlanCatalogueSchema,
|
|
203
|
+
export { personalPlanDisplayPriceSchema, personalPlanCatalogueSchema, } from './products/plans.js';
|
|
204
204
|
export * from './products/checkout.js';
|
|
205
205
|
export * from './organizations.js';
|
|
206
206
|
export * from './safety.js';
|
|
@@ -144,6 +144,15 @@ export const moneySchema = z
|
|
|
144
144
|
* estimated from the audio, and a provider billed by the audio it carries (or
|
|
145
145
|
* by tokens) never reports it.
|
|
146
146
|
*
|
|
147
|
+
* **`input_transcription_milliseconds` is a second model's reading** (added in
|
|
148
|
+
* contract set 3.9.0, mirroring `@kaana.ai/contracts` 4.1.0): milliseconds of a
|
|
149
|
+
* realtime session's input audio that the deployment's own TRANSCRIPTION model
|
|
150
|
+
* transcribed, when the provider bills that model apart from the session model
|
|
151
|
+
* (OpenAI bills input transcription at the ASR model's price). The session
|
|
152
|
+
* model's `audio_input_tokens` and this unit are two charges for two models,
|
|
153
|
+
* each read once, so the partition rule holds. A provider whose session price
|
|
154
|
+
* already includes the transcription (xAI's Voice Agent) never reports it.
|
|
155
|
+
*
|
|
147
156
|
* No refinement in this package can enforce it, and saying so is part of the
|
|
148
157
|
* rule: a nested report and a disjoint one are the same four non-negative
|
|
149
158
|
* integers, so no predicate over a single report can tell them apart. The two
|
|
@@ -181,6 +190,7 @@ export const USAGE_UNITS = [
|
|
|
181
190
|
'cached_audio_input_tokens',
|
|
182
191
|
'audio_output_tokens',
|
|
183
192
|
'session_milliseconds',
|
|
193
|
+
'input_transcription_milliseconds',
|
|
184
194
|
];
|
|
185
195
|
export const usageUnitSchema = z.enum(USAGE_UNITS);
|
|
186
196
|
/**
|
|
@@ -99,4 +99,4 @@
|
|
|
99
99
|
* change to, say, the catalogue reject every in-flight inference request; the
|
|
100
100
|
* per-shape `schemaVersion` is what a message is validated against.
|
|
101
101
|
*/
|
|
102
|
-
export const INFERENCE_CONTRACT_VERSION = '3.
|
|
102
|
+
export const INFERENCE_CONTRACT_VERSION = '3.9.0';
|
|
@@ -1,22 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
'public_body',
|
|
7
|
-
'community',
|
|
8
|
-
'other',
|
|
9
|
-
];
|
|
10
|
-
export const ORGANIZATION_STATUSES = ['active', 'inactive', 'merged', 'suppressed'];
|
|
11
|
-
export const ORGANIZATION_CLAIM_STATUSES = ['unclaimed', 'claimed'];
|
|
12
|
-
export const ORGANIZATION_ALIAS_KINDS = [
|
|
13
|
-
'name_variant',
|
|
14
|
-
'former_name',
|
|
15
|
-
'localized_name',
|
|
16
|
-
'marketing_name',
|
|
17
|
-
'misspelling',
|
|
18
|
-
];
|
|
19
|
-
export const ORGANIZATION_CLAIM_DECISIONS = ['pending', 'approved', 'rejected'];
|
|
2
|
+
// The Zod-free half of this contract lives in `./client/` (`@oxy.so/contracts/client`);
|
|
3
|
+
// it is re-exported here so the root entry is unchanged.
|
|
4
|
+
import { ORGANIZATION_TYPES, ORGANIZATION_STATUSES, ORGANIZATION_CLAIM_STATUSES, ORGANIZATION_ALIAS_KINDS, ORGANIZATION_CLAIM_DECISIONS, ORGANIZATION_NAME_MAX_LENGTH, ORGANIZATION_ERROR_CODES, } from './client/organizations.js';
|
|
5
|
+
export { ORGANIZATION_TYPES, ORGANIZATION_STATUSES, ORGANIZATION_CLAIM_STATUSES, ORGANIZATION_ALIAS_KINDS, ORGANIZATION_CLAIM_DECISIONS, ORGANIZATION_NAME_MAX_LENGTH, ORGANIZATION_ERROR_CODES, } from './client/organizations.js';
|
|
20
6
|
const id = z.string().min(1).max(128);
|
|
21
7
|
const optionalText = (max) => z.string().trim().min(1).max(max).nullable().optional();
|
|
22
8
|
export const organizationAliasSchema = z
|
|
@@ -26,7 +12,6 @@ export const organizationAliasSchema = z
|
|
|
26
12
|
language: z.string().min(2).max(35).optional(),
|
|
27
13
|
})
|
|
28
14
|
.strict();
|
|
29
|
-
export const ORGANIZATION_NAME_MAX_LENGTH = 300;
|
|
30
15
|
export const organizationNameSchema = z
|
|
31
16
|
.string()
|
|
32
17
|
.trim()
|
|
@@ -168,18 +153,6 @@ export const organizationMergeSchema = z
|
|
|
168
153
|
evidenceReference: z.string().trim().min(1).max(1000),
|
|
169
154
|
})
|
|
170
155
|
.strict();
|
|
171
|
-
export const ORGANIZATION_ERROR_CODES = [
|
|
172
|
-
'bad_request',
|
|
173
|
-
'unauthorized',
|
|
174
|
-
'forbidden',
|
|
175
|
-
'not_found',
|
|
176
|
-
'unknown_route',
|
|
177
|
-
'conflict',
|
|
178
|
-
'validation_failed',
|
|
179
|
-
'rate_limited',
|
|
180
|
-
'internal_error',
|
|
181
|
-
'service_unavailable',
|
|
182
|
-
];
|
|
183
156
|
export const organizationErrorSchema = z
|
|
184
157
|
.object({
|
|
185
158
|
error: z
|
|
@@ -30,16 +30,3 @@ export const personalPlanCheckoutResultSchema = z.discriminatedUnion('state', [
|
|
|
30
30
|
z.object({ state: z.literal('closed'), intentId: z.string().min(1).max(160) }).strict(),
|
|
31
31
|
z.object({ state: z.literal('fulfilled'), intentId: z.string().min(1).max(160) }).strict(),
|
|
32
32
|
]);
|
|
33
|
-
/** Explicit individual product offer; still owned and paid through Oxy. */
|
|
34
|
-
export const productPlanCheckoutRequestSchema = personalPlanCheckoutRequestSchema
|
|
35
|
-
.extend({
|
|
36
|
-
productId: z.string().min(1).max(160),
|
|
37
|
-
})
|
|
38
|
-
.strict();
|
|
39
|
-
export const productSubscriptionRenewalRequestSchema = z
|
|
40
|
-
.object({
|
|
41
|
-
expectedSubjectAccountId: oxyAccountIdSchema,
|
|
42
|
-
sourceId: z.string().min(1).max(160),
|
|
43
|
-
idempotencyKey: personalPlanCheckoutRequestSchema.shape.idempotencyKey,
|
|
44
|
-
})
|
|
45
|
-
.strict();
|
|
@@ -16,7 +16,7 @@ export const personalPlanCatalogueSchema = z
|
|
|
16
16
|
.object({
|
|
17
17
|
schemaVersion: z.literal(1),
|
|
18
18
|
state: z.enum(['unconfigured', 'configured']),
|
|
19
|
-
purchase: z.
|
|
19
|
+
purchase: z.literal('unavailable'),
|
|
20
20
|
plans: z.array(z
|
|
21
21
|
.object({
|
|
22
22
|
offerId: z.string().min(1).max(160),
|
|
@@ -33,19 +33,3 @@ export const personalPlanCatalogueSchema = z
|
|
|
33
33
|
})
|
|
34
34
|
.strict()
|
|
35
35
|
.refine((value) => (value.state === 'unconfigured') === (value.plans.length === 0), 'Catalogue state must match published plans');
|
|
36
|
-
export const productPlanCatalogueSchema = z
|
|
37
|
-
.object({
|
|
38
|
-
schemaVersion: z.literal(1),
|
|
39
|
-
productId: z.string().min(1).max(160),
|
|
40
|
-
purchase: z.enum(['unavailable', 'available']),
|
|
41
|
-
plans: z.array(z
|
|
42
|
-
.object({
|
|
43
|
-
productId: z.string().min(1).max(160),
|
|
44
|
-
offerId: z.string().min(1).max(160),
|
|
45
|
-
offerVersion: z.number().int().positive().safe(),
|
|
46
|
-
displayName: z.string().min(1).max(100),
|
|
47
|
-
price: personalPlanDisplayPriceSchema.extend({ interval: z.enum(['month', 'year']) }),
|
|
48
|
-
})
|
|
49
|
-
.strict()),
|
|
50
|
-
})
|
|
51
|
-
.strict();
|
package/dist/esm/reputation.js
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
import { z } from 'zod';
|
|
42
42
|
import { userNameSchema } from './userResponse.js';
|
|
43
43
|
import { reputationConductSchema, reputationContextualInfluenceSchema, reputationContributionSchema, reputationPersonhoodSchema, reputationReportingSchema, reputationReviewingSchema, } from './moderationReputation.js';
|
|
44
|
+
export { isFullReputationBalance } from './client/reputation.js';
|
|
44
45
|
/* -------------------------------------------------------------------------- */
|
|
45
46
|
/* Closed value sets */
|
|
46
47
|
/* -------------------------------------------------------------------------- */
|
|
@@ -170,38 +171,6 @@ export const reputationBalanceSchema = z.object({
|
|
|
170
171
|
reviewing: reputationReviewingSchema.optional(),
|
|
171
172
|
contextualInfluence: reputationContextualInfluenceSchema.optional(),
|
|
172
173
|
});
|
|
173
|
-
/**
|
|
174
|
-
* The fields the full {@link ReputationBalance} carries beyond the public
|
|
175
|
-
* {@link ReputationBalanceSummary} that the API sends ALL-OR-NOTHING. The
|
|
176
|
-
* runtime discriminant between the two views.
|
|
177
|
-
*
|
|
178
|
-
* The V2 blocks (`conduct`, `contribution`, …) are deliberately NOT listed:
|
|
179
|
-
* they are optional on the wire, so requiring them here would make a balance
|
|
180
|
-
* from a server that predates them fail to narrow, hiding the whole private
|
|
181
|
-
* view. Read a V2 block by checking that block.
|
|
182
|
-
*/
|
|
183
|
-
const FULL_BALANCE_FIELDS = [
|
|
184
|
-
'positive',
|
|
185
|
-
'negative',
|
|
186
|
-
'breakdown',
|
|
187
|
-
'influence',
|
|
188
|
-
'reliability',
|
|
189
|
-
'recalculatedAt',
|
|
190
|
-
'updatedAt',
|
|
191
|
-
];
|
|
192
|
-
/**
|
|
193
|
-
* Whether a balance came back as the SUBJECT view, and so carries the
|
|
194
|
-
* breakdown / influence / reliability blocks.
|
|
195
|
-
*
|
|
196
|
-
* Checks every extra field rather than one representative: the point of the
|
|
197
|
-
* guard is that the caller then dereferences those blocks, so a partial payload
|
|
198
|
-
* must not narrow.
|
|
199
|
-
*
|
|
200
|
-
* @param balance - A balance from `getReputationBalance`.
|
|
201
|
-
*/
|
|
202
|
-
export function isFullReputationBalance(balance) {
|
|
203
|
-
return FULL_BALANCE_FIELDS.every((field) => field in balance);
|
|
204
|
-
}
|
|
205
174
|
export const reputationRuleSchema = z.object({
|
|
206
175
|
actionType: z.string(),
|
|
207
176
|
points: z.number(),
|