@frockbot/configuration-core 0.1.3 → 0.1.4
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/package.json +3 -3
- package/src/bot-identity.test.ts +1 -2
- package/src/index.test.ts +381 -319
- package/src/index.ts +438 -439
- package/src/package-settings.test.ts +100 -0
- package/src/package-settings.ts +209 -28
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import type { PackageSettingDefinition } from "@frockbot/kernel-composition";
|
|
3
3
|
import {
|
|
4
|
+
decodeInstalledPackageSettingsPatchV1,
|
|
5
|
+
decodeModelBindingV1,
|
|
4
6
|
decodePackageSettingsPatchV1,
|
|
5
7
|
decodePackageSettingValuesV1,
|
|
6
8
|
emptyPackageSettingValuesV1,
|
|
@@ -128,6 +130,104 @@ describe("the Package setting values codec", () => {
|
|
|
128
130
|
expect(() =>
|
|
129
131
|
decodePackageSettingsPatchV1(botScoped, { tone: "terse" }),
|
|
130
132
|
).toThrow(/not a User-level setting/);
|
|
133
|
+
expect(
|
|
134
|
+
decodePackageSettingsPatchV1(botScoped, { tone: "terse" }, "bot"),
|
|
135
|
+
).toEqual({ tone: "terse" });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("validates the model role's exact structured value at either scope", () => {
|
|
139
|
+
const modelRole = [
|
|
140
|
+
definition({
|
|
141
|
+
id: "model",
|
|
142
|
+
role: "model",
|
|
143
|
+
scopes: ["user", "bot"],
|
|
144
|
+
schema: {
|
|
145
|
+
type: "object",
|
|
146
|
+
properties: {
|
|
147
|
+
connectionId: { type: "string" },
|
|
148
|
+
providerModelId: { type: "string" },
|
|
149
|
+
},
|
|
150
|
+
required: ["connectionId", "providerModelId"],
|
|
151
|
+
additionalProperties: false,
|
|
152
|
+
},
|
|
153
|
+
}),
|
|
154
|
+
];
|
|
155
|
+
const model = {
|
|
156
|
+
connectionId: "ollama-work",
|
|
157
|
+
providerModelId: "glm-5.3-flash:cloud",
|
|
158
|
+
};
|
|
159
|
+
expect(decodePackageSettingsPatchV1(modelRole, { model })).toEqual({
|
|
160
|
+
model,
|
|
161
|
+
});
|
|
162
|
+
expect(decodePackageSettingsPatchV1(modelRole, { model }, "bot")).toEqual({
|
|
163
|
+
model,
|
|
164
|
+
});
|
|
165
|
+
expect(() =>
|
|
166
|
+
decodePackageSettingsPatchV1(modelRole, {
|
|
167
|
+
model: { ...model, extra: true },
|
|
168
|
+
}),
|
|
169
|
+
).toThrow(/invalid fields/);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("rejects hidden, symbol, inherited, and prototype-shaped fields", () => {
|
|
173
|
+
const hidden = { verbose: true };
|
|
174
|
+
Object.defineProperty(hidden, "secret", { value: "hidden" });
|
|
175
|
+
expect(() => decodePackageSettingsPatchV1(declared, hidden)).toThrow(
|
|
176
|
+
/invalid fields/,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const symbol = { verbose: true, [Symbol("extra")]: true };
|
|
180
|
+
expect(() => decodePackageSettingsPatchV1(declared, symbol)).toThrow(
|
|
181
|
+
/invalid fields/,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const inherited = Object.create({ verbose: true });
|
|
185
|
+
expect(() => decodePackageSettingsPatchV1(declared, inherited)).toThrow(
|
|
186
|
+
/plain object|inherited fields/,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
expect(() =>
|
|
190
|
+
decodeModelBindingV1({
|
|
191
|
+
connectionId: "constructor",
|
|
192
|
+
providerModelId: "model-1",
|
|
193
|
+
}),
|
|
194
|
+
).toThrow(/connectionId is invalid/);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("validates a Bot command against the installed manifest version", () => {
|
|
198
|
+
const installations = [{ packageId: "preferences", version: "1.0.0" }];
|
|
199
|
+
const packages = [
|
|
200
|
+
{ packageId: "preferences", version: "1.0.0", settings: declared },
|
|
201
|
+
];
|
|
202
|
+
const validate = (packageId: string, values: unknown) =>
|
|
203
|
+
decodeInstalledPackageSettingsPatchV1({
|
|
204
|
+
packageId,
|
|
205
|
+
values,
|
|
206
|
+
scope: "bot",
|
|
207
|
+
installations,
|
|
208
|
+
packages,
|
|
209
|
+
});
|
|
210
|
+
const botSetting = [
|
|
211
|
+
definition({ id: "tone", scopes: ["bot"], schema: { type: "string" } }),
|
|
212
|
+
];
|
|
213
|
+
packages[0] = {
|
|
214
|
+
packageId: "preferences",
|
|
215
|
+
version: "1.0.0",
|
|
216
|
+
settings: botSetting,
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
expect(validate("preferences", { tone: "terse" })).toEqual({
|
|
220
|
+
tone: "terse",
|
|
221
|
+
});
|
|
222
|
+
expect(() => validate("missing", { tone: "terse" })).toThrow(
|
|
223
|
+
'Package "missing" is not installed',
|
|
224
|
+
);
|
|
225
|
+
expect(() => validate("preferences", { unknown: "value" })).toThrow(
|
|
226
|
+
/not declared/,
|
|
227
|
+
);
|
|
228
|
+
expect(() => validate("preferences", { tone: 42 })).toThrow(
|
|
229
|
+
/must be a string/,
|
|
230
|
+
);
|
|
131
231
|
});
|
|
132
232
|
|
|
133
233
|
test("refuses a secret, whatever its schema says", () => {
|
package/src/package-settings.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Package-level setting *values*: the durable configuration one installed
|
|
2
|
-
// Package carries for one User.
|
|
2
|
+
// Package carries for one User or one of that User's Bots.
|
|
3
3
|
//
|
|
4
4
|
// A manifest declares `configuration.settings` — the knobs a Package offers —
|
|
5
5
|
// and until now nothing anywhere held a value for one, so every Package read
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
// command and that durable state.
|
|
8
8
|
//
|
|
9
9
|
// WHAT BELONGS HERE, AND WHAT DOES NOT. A Package-level setting is
|
|
10
|
-
// configuration scoped to the User: a
|
|
11
|
-
// root. A *secret* never is. A credential reaches the keyring through
|
|
12
|
-
// Connection and only through one, so a definition that declares itself
|
|
13
|
-
// is refused here with that answer rather than stored.
|
|
10
|
+
// configuration scoped to the User or Bot: a model choice, a ceiling, an
|
|
11
|
+
// endpoint root. A *secret* never is. A credential reaches the keyring through
|
|
12
|
+
// a Connection and only through one, so a definition that declares itself
|
|
13
|
+
// secret is refused here with that answer rather than stored.
|
|
14
14
|
//
|
|
15
15
|
// STRICT IN, LENIENT OUT. A write is validated against the exact schema the
|
|
16
16
|
// Package declared: an unknown setting id, a wrong type, a value outside its
|
|
@@ -26,23 +26,30 @@ import type {
|
|
|
26
26
|
PackageSettingSchemaValue,
|
|
27
27
|
} from "@frockbot/kernel-composition";
|
|
28
28
|
import { ConfigurationDecodeError } from "./errors.js";
|
|
29
|
-
import { isPublicIdentifier } from "./identifiers.js";
|
|
29
|
+
import { isConnectionIdentifier, isPublicIdentifier } from "./identifiers.js";
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* document, which belongs in a Package's own durable state rather than in the
|
|
35
|
-
* User's settings bag.
|
|
32
|
+
* The provider-neutral model identity selected by a model-role Package
|
|
33
|
+
* setting. This is a reference to a Connection, never a credential.
|
|
36
34
|
*/
|
|
37
|
-
export
|
|
35
|
+
export interface ModelBindingV1 {
|
|
36
|
+
connectionId: string;
|
|
37
|
+
providerModelId: string;
|
|
38
|
+
}
|
|
38
39
|
|
|
39
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* What one Package-level setting may hold. Ordinary settings remain scalars;
|
|
42
|
+
* ADR 0019 adds exactly one structured exception, the model role's binding.
|
|
43
|
+
*/
|
|
44
|
+
export type PackageSettingValueV1 = string | number | boolean | ModelBindingV1;
|
|
45
|
+
|
|
46
|
+
/** The durable record of one installed Package's setting values at one scope. */
|
|
40
47
|
export interface PackageSettingValuesV1 {
|
|
41
48
|
schemaVersion: 1;
|
|
42
49
|
values: Record<string, PackageSettingValueV1>;
|
|
43
50
|
}
|
|
44
51
|
|
|
45
|
-
/** The durable per-
|
|
52
|
+
/** The durable per-scope ceiling on how many values one Package may hold. */
|
|
46
53
|
export const MAX_PACKAGE_SETTINGS_V1 = 32;
|
|
47
54
|
|
|
48
55
|
/** The ceiling on one text value, whatever the schema's own `maxLength`. */
|
|
@@ -57,7 +64,63 @@ function record(value: unknown, label: string): Record<string, unknown> {
|
|
|
57
64
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
58
65
|
throw new ConfigurationDecodeError(`${label} must be an object`);
|
|
59
66
|
}
|
|
60
|
-
|
|
67
|
+
const candidate = value as Record<string, unknown>;
|
|
68
|
+
const prototype = Object.getPrototypeOf(candidate);
|
|
69
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
70
|
+
throw new ConfigurationDecodeError(`${label} must be a plain object`);
|
|
71
|
+
}
|
|
72
|
+
for (const key in candidate) {
|
|
73
|
+
if (!Object.hasOwn(candidate, key)) {
|
|
74
|
+
throw new ConfigurationDecodeError(`${label} has inherited fields`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const key of Reflect.ownKeys(candidate)) {
|
|
78
|
+
const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
|
|
79
|
+
if (
|
|
80
|
+
typeof key !== "string" ||
|
|
81
|
+
!descriptor ||
|
|
82
|
+
!("value" in descriptor) ||
|
|
83
|
+
!descriptor.enumerable
|
|
84
|
+
) {
|
|
85
|
+
throw new ConfigurationDecodeError(`${label} has invalid fields`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return candidate;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function boundedText(value: unknown, label: string, maximum: number): string {
|
|
92
|
+
if (typeof value !== "string") {
|
|
93
|
+
throw new ConfigurationDecodeError(`${label} must be a string`);
|
|
94
|
+
}
|
|
95
|
+
const normalized = value.trim();
|
|
96
|
+
if (!normalized || normalized.length > maximum) {
|
|
97
|
+
throw new ConfigurationDecodeError(`${label} is invalid`);
|
|
98
|
+
}
|
|
99
|
+
return normalized;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The exact model binding DTO, decoded wherever it crosses a durable seam. */
|
|
103
|
+
export function decodeModelBindingV1(value: unknown): ModelBindingV1 {
|
|
104
|
+
const binding = record(value, "model");
|
|
105
|
+
const keys = Reflect.ownKeys(binding);
|
|
106
|
+
if (
|
|
107
|
+
keys.length !== 2 ||
|
|
108
|
+
!Object.hasOwn(binding, "connectionId") ||
|
|
109
|
+
!Object.hasOwn(binding, "providerModelId")
|
|
110
|
+
) {
|
|
111
|
+
throw new ConfigurationDecodeError("model has invalid fields");
|
|
112
|
+
}
|
|
113
|
+
if (!isConnectionIdentifier(binding.connectionId)) {
|
|
114
|
+
throw new ConfigurationDecodeError("model.connectionId is invalid");
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
connectionId: binding.connectionId,
|
|
118
|
+
providerModelId: boundedText(
|
|
119
|
+
binding.providerModelId,
|
|
120
|
+
"model.providerModelId",
|
|
121
|
+
256,
|
|
122
|
+
),
|
|
123
|
+
};
|
|
61
124
|
}
|
|
62
125
|
|
|
63
126
|
/**
|
|
@@ -169,13 +232,13 @@ function checkNumber(
|
|
|
169
232
|
* One value, against the definition its Package declared.
|
|
170
233
|
*
|
|
171
234
|
* The scope check is the constitution's, not a convenience: Package
|
|
172
|
-
* availability is User-level,
|
|
173
|
-
*
|
|
174
|
-
* one to a Bot, and neither has a home here.
|
|
235
|
+
* availability is User-level, while each value still has the manifest scope
|
|
236
|
+
* that owns it. A `connection`-scoped value always belongs to a Connection.
|
|
175
237
|
*/
|
|
176
238
|
export function decodePackageSettingValueV1(
|
|
177
239
|
definition: PackageSettingDefinition,
|
|
178
240
|
value: unknown,
|
|
241
|
+
scope: "user" | "bot" = "user",
|
|
179
242
|
): PackageSettingValueV1 {
|
|
180
243
|
const settingId = definition.id;
|
|
181
244
|
// Defensive, and deliberately not dead: `PackageSettingDefinition` carries no
|
|
@@ -187,13 +250,14 @@ export function decodePackageSettingValueV1(
|
|
|
187
250
|
`Package setting "${settingId}" is a secret: use a Connection`,
|
|
188
251
|
);
|
|
189
252
|
}
|
|
190
|
-
if (!definition.scopes.includes(
|
|
253
|
+
if (!definition.scopes.includes(scope)) {
|
|
191
254
|
throw new ConfigurationDecodeError(
|
|
192
255
|
definition.scopes.includes("connection")
|
|
193
256
|
? `Package setting "${settingId}" is Connection-scoped: use a Connection`
|
|
194
|
-
: `Package setting "${settingId}" is not a User-level setting`,
|
|
257
|
+
: `Package setting "${settingId}" is not a ${scope === "user" ? "User" : "Bot"}-level setting`,
|
|
195
258
|
);
|
|
196
259
|
}
|
|
260
|
+
if (definition.role === "model") return decodeModelBindingV1(value);
|
|
197
261
|
const schema = definition.schema;
|
|
198
262
|
const declared = scalarType(schema, settingId);
|
|
199
263
|
if (
|
|
@@ -250,13 +314,14 @@ function definitionsById(
|
|
|
250
314
|
}
|
|
251
315
|
|
|
252
316
|
/**
|
|
253
|
-
* The `values` a
|
|
254
|
-
* update, so only the ids it names are decoded. Every one
|
|
255
|
-
* setting the Package declares at
|
|
317
|
+
* The `values` a User- or Bot-scoped Package settings command carries: a
|
|
318
|
+
* *partial* update, so only the ids it names are decoded. Every one must be a
|
|
319
|
+
* setting the installed Package version declares at the requested scope.
|
|
256
320
|
*/
|
|
257
321
|
export function decodePackageSettingsPatchV1(
|
|
258
322
|
definitions: readonly PackageSettingDefinition[],
|
|
259
323
|
input: unknown,
|
|
324
|
+
scope: "user" | "bot" = "user",
|
|
260
325
|
): Record<string, PackageSettingValueV1> {
|
|
261
326
|
const values = record(input, "values");
|
|
262
327
|
const entries = Object.entries(values);
|
|
@@ -264,7 +329,7 @@ export function decodePackageSettingsPatchV1(
|
|
|
264
329
|
throw new ConfigurationDecodeError("Package settings are too many");
|
|
265
330
|
}
|
|
266
331
|
const declared = definitionsById(definitions);
|
|
267
|
-
const decoded:
|
|
332
|
+
const decoded: Array<[string, PackageSettingValueV1]> = [];
|
|
268
333
|
for (const [settingId, value] of entries) {
|
|
269
334
|
if (!isPublicIdentifier(settingId)) {
|
|
270
335
|
throw new ConfigurationDecodeError("Package setting id is invalid");
|
|
@@ -275,15 +340,129 @@ export function decodePackageSettingsPatchV1(
|
|
|
275
340
|
`Package setting "${settingId}" is not declared by this Package`,
|
|
276
341
|
);
|
|
277
342
|
}
|
|
278
|
-
decoded[
|
|
343
|
+
decoded.push([
|
|
344
|
+
settingId,
|
|
345
|
+
decodePackageSettingValueV1(definition, value, scope),
|
|
346
|
+
]);
|
|
347
|
+
}
|
|
348
|
+
return Object.fromEntries(decoded);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** A non-empty list of declared setting ids to remove at one scope. */
|
|
352
|
+
export function decodePackageSettingIdsV1(
|
|
353
|
+
definitions: readonly PackageSettingDefinition[],
|
|
354
|
+
input: unknown,
|
|
355
|
+
scope: "user" | "bot" = "user",
|
|
356
|
+
): string[] {
|
|
357
|
+
if (!Array.isArray(input) || input.length === 0) {
|
|
358
|
+
throw new ConfigurationDecodeError("unset names no setting");
|
|
359
|
+
}
|
|
360
|
+
if (input.length > MAX_PACKAGE_SETTINGS_V1) {
|
|
361
|
+
throw new ConfigurationDecodeError("unset is too large");
|
|
362
|
+
}
|
|
363
|
+
const declared = definitionsById(definitions);
|
|
364
|
+
const decoded = input.map((settingId) => {
|
|
365
|
+
if (!isPublicIdentifier(settingId)) {
|
|
366
|
+
throw new ConfigurationDecodeError("Package setting id is invalid");
|
|
367
|
+
}
|
|
368
|
+
const definition = declared.get(settingId);
|
|
369
|
+
if (!definition) {
|
|
370
|
+
throw new ConfigurationDecodeError(
|
|
371
|
+
`Package setting "${settingId}" is not declared by this Package`,
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
if (!definition.scopes.includes(scope)) {
|
|
375
|
+
throw new ConfigurationDecodeError(
|
|
376
|
+
`Package setting "${settingId}" is not a ${scope === "user" ? "User" : "Bot"}-level setting`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return settingId;
|
|
380
|
+
});
|
|
381
|
+
if (new Set(decoded).size !== decoded.length) {
|
|
382
|
+
throw new ConfigurationDecodeError("unset repeats a setting");
|
|
279
383
|
}
|
|
280
384
|
return decoded;
|
|
281
385
|
}
|
|
282
386
|
|
|
387
|
+
export interface InstalledPackageSettingsV1 {
|
|
388
|
+
packageId: string;
|
|
389
|
+
version: string;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export interface PackageSettingsDefinitionV1 {
|
|
393
|
+
packageId: string;
|
|
394
|
+
version: string;
|
|
395
|
+
settings: readonly PackageSettingDefinition[];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* The installed-version lookup shared by the User and Bot authorities before
|
|
400
|
+
* either merges a partial settings command. The lookup and schema validation
|
|
401
|
+
* are one fail-closed operation, so neither writer can accidentally validate
|
|
402
|
+
* against a catalog version the User did not install.
|
|
403
|
+
*/
|
|
404
|
+
export function decodeInstalledPackageSettingsPatchV1(input: {
|
|
405
|
+
packageId: string;
|
|
406
|
+
values: unknown;
|
|
407
|
+
scope: "user" | "bot";
|
|
408
|
+
installations: readonly InstalledPackageSettingsV1[];
|
|
409
|
+
packages: readonly PackageSettingsDefinitionV1[];
|
|
410
|
+
}): Record<string, PackageSettingValueV1> {
|
|
411
|
+
const installation = input.installations.find(
|
|
412
|
+
(candidate) => candidate.packageId === input.packageId,
|
|
413
|
+
);
|
|
414
|
+
if (!installation) {
|
|
415
|
+
throw new ConfigurationDecodeError(
|
|
416
|
+
`Package "${input.packageId}" is not installed`,
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
const pkg = input.packages.find(
|
|
420
|
+
(candidate) =>
|
|
421
|
+
candidate.packageId === installation.packageId &&
|
|
422
|
+
candidate.version === installation.version,
|
|
423
|
+
);
|
|
424
|
+
if (!pkg) {
|
|
425
|
+
throw new ConfigurationDecodeError(
|
|
426
|
+
`Installed Package "${installation.packageId}" version "${installation.version}" has no manifest`,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
return decodePackageSettingsPatchV1(pkg.settings, input.values, input.scope);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** Installed-version lookup and validation for a settings deletion. */
|
|
433
|
+
export function decodeInstalledPackageSettingIdsV1(input: {
|
|
434
|
+
packageId: string;
|
|
435
|
+
unset: unknown;
|
|
436
|
+
scope: "user" | "bot";
|
|
437
|
+
installations: readonly InstalledPackageSettingsV1[];
|
|
438
|
+
packages: readonly PackageSettingsDefinitionV1[];
|
|
439
|
+
}): string[] {
|
|
440
|
+
const installation = input.installations.find(
|
|
441
|
+
(candidate) => candidate.packageId === input.packageId,
|
|
442
|
+
);
|
|
443
|
+
if (!installation) {
|
|
444
|
+
throw new ConfigurationDecodeError(
|
|
445
|
+
`Package "${input.packageId}" is not installed`,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
const pkg = input.packages.find(
|
|
449
|
+
(candidate) =>
|
|
450
|
+
candidate.packageId === installation.packageId &&
|
|
451
|
+
candidate.version === installation.version,
|
|
452
|
+
);
|
|
453
|
+
if (!pkg) {
|
|
454
|
+
throw new ConfigurationDecodeError(
|
|
455
|
+
`Installed Package "${installation.packageId}" version "${installation.version}" has no manifest`,
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
return decodePackageSettingIdsV1(pkg.settings, input.unset, input.scope);
|
|
459
|
+
}
|
|
460
|
+
|
|
283
461
|
/** The durable record, decoded whole. */
|
|
284
462
|
export function decodePackageSettingValuesV1(
|
|
285
463
|
definitions: readonly PackageSettingDefinition[],
|
|
286
464
|
input: unknown,
|
|
465
|
+
scope: "user" | "bot" = "user",
|
|
287
466
|
): PackageSettingValuesV1 {
|
|
288
467
|
const value = record(input, "Package setting values");
|
|
289
468
|
if (value.schemaVersion !== 1) {
|
|
@@ -292,15 +471,15 @@ export function decodePackageSettingValuesV1(
|
|
|
292
471
|
);
|
|
293
472
|
}
|
|
294
473
|
if (
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
)
|
|
474
|
+
Reflect.ownKeys(value).length !== 2 ||
|
|
475
|
+
!Object.hasOwn(value, "schemaVersion") ||
|
|
476
|
+
!Object.hasOwn(value, "values")
|
|
298
477
|
) {
|
|
299
478
|
throw new ConfigurationDecodeError("Package setting values are invalid");
|
|
300
479
|
}
|
|
301
480
|
return {
|
|
302
481
|
schemaVersion: 1,
|
|
303
|
-
values: decodePackageSettingsPatchV1(definitions, value.values),
|
|
482
|
+
values: decodePackageSettingsPatchV1(definitions, value.values, scope),
|
|
304
483
|
};
|
|
305
484
|
}
|
|
306
485
|
|
|
@@ -317,6 +496,7 @@ export function decodePackageSettingValuesV1(
|
|
|
317
496
|
export function resolvePackageSettingValuesV1(
|
|
318
497
|
definitions: readonly PackageSettingDefinition[],
|
|
319
498
|
stored: Readonly<Record<string, unknown>> | undefined,
|
|
499
|
+
scope: "user" | "bot" = "user",
|
|
320
500
|
): Record<string, PackageSettingValueV1> {
|
|
321
501
|
if (!stored) return {};
|
|
322
502
|
const resolved: Record<string, PackageSettingValueV1> = {};
|
|
@@ -326,6 +506,7 @@ export function resolvePackageSettingValuesV1(
|
|
|
326
506
|
resolved[definition.id] = decodePackageSettingValueV1(
|
|
327
507
|
definition,
|
|
328
508
|
stored[definition.id],
|
|
509
|
+
scope,
|
|
329
510
|
);
|
|
330
511
|
} catch {
|
|
331
512
|
continue;
|