@frockbot/configuration-core 0.1.4 → 0.2.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/package.json +3 -3
- package/src/bot-identity.test.ts +2 -2
- package/src/index.test.ts +186 -0
- package/src/index.ts +227 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/configuration-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@frockbot/connection-core": "0.
|
|
16
|
-
"@frockbot/kernel-composition": "0.
|
|
15
|
+
"@frockbot/connection-core": "0.2.0",
|
|
16
|
+
"@frockbot/kernel-composition": "0.2.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/bun": "1.3.6",
|
package/src/bot-identity.test.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Bot identity: the title, the name's provenance, and the sidebar-hidden flag.
|
|
2
2
|
//
|
|
3
3
|
// The first test is the one that matters most: a Bot settings record written
|
|
4
|
-
// before any of this existed must still decode
|
|
5
|
-
//
|
|
4
|
+
// before any of this existed must still decode. Optional additions need no
|
|
5
|
+
// transform; incompatible stored shapes use explicit forward migrations.
|
|
6
6
|
import { describe, expect, test } from "bun:test";
|
|
7
7
|
import {
|
|
8
8
|
applyBotProfilePatchV1,
|
package/src/index.test.ts
CHANGED
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
decodeUserConfigurationReadRpcV1,
|
|
19
19
|
decodeUserSettingsViewV1,
|
|
20
20
|
initializeBotSettingsV1,
|
|
21
|
+
migrateStoredBotSettingsV1,
|
|
22
|
+
migrateStoredUserSettingsV1,
|
|
21
23
|
modelBindingFailureV1,
|
|
22
24
|
resolveBotExecutionPlanV1,
|
|
23
25
|
resolveBotModelBindingV1,
|
|
@@ -32,6 +34,190 @@ import {
|
|
|
32
34
|
isRpcIdentifier,
|
|
33
35
|
} from "./identifiers.js";
|
|
34
36
|
|
|
37
|
+
describe("stored configuration migrations", () => {
|
|
38
|
+
test("migrates the pre-account-wide User record without honouring removed features", () => {
|
|
39
|
+
// Literal durable shape from eb0283edcce5daea976a21a9f6a6414bedc6e2bc,
|
|
40
|
+
// the first parent of PR #134's merge commit.
|
|
41
|
+
const stored = {
|
|
42
|
+
schemaVersion: 1,
|
|
43
|
+
revision: 7,
|
|
44
|
+
profile: { name: "Existing User" },
|
|
45
|
+
packages: [
|
|
46
|
+
{
|
|
47
|
+
packageId: "provider-ollama-cloud",
|
|
48
|
+
version: "0.0.1",
|
|
49
|
+
state: "installed" as const,
|
|
50
|
+
values: { "web-search-max-results": 5 },
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
connections: [
|
|
54
|
+
{
|
|
55
|
+
connectionId: "ollama-1",
|
|
56
|
+
packageId: "provider-ollama-cloud",
|
|
57
|
+
connectionTypeId: "ollama-cloud-account",
|
|
58
|
+
displayName: "Work",
|
|
59
|
+
state: "ready" as const,
|
|
60
|
+
safeMetadata: {
|
|
61
|
+
region: "au",
|
|
62
|
+
dependentAssignments: [
|
|
63
|
+
{
|
|
64
|
+
botId: "bot-1",
|
|
65
|
+
generation: "assignment-1",
|
|
66
|
+
packageId: "provider-ollama-cloud",
|
|
67
|
+
capabilityId: "ollama-cloud-models",
|
|
68
|
+
claimOrder: 7,
|
|
69
|
+
status: "acknowledged",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
newBotModelTemplate: {
|
|
76
|
+
connectionId: "ollama-1",
|
|
77
|
+
providerModelId: "glm-5.3-flash:cloud",
|
|
78
|
+
},
|
|
79
|
+
newBotModelTemplateSource: "user",
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
expect(
|
|
83
|
+
decodeUserSettingsViewV1(migrateStoredUserSettingsV1(stored)),
|
|
84
|
+
).toEqual({
|
|
85
|
+
schemaVersion: 1,
|
|
86
|
+
revision: 7,
|
|
87
|
+
profile: { name: "Existing User" },
|
|
88
|
+
packages: stored.packages,
|
|
89
|
+
connections: [
|
|
90
|
+
{
|
|
91
|
+
...stored.connections[0],
|
|
92
|
+
safeMetadata: { region: "au" },
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
expect(stored.connections[0]?.safeMetadata).toHaveProperty(
|
|
97
|
+
"dependentAssignments",
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("keeps unknown User, Package, and Connection fields strict", () => {
|
|
102
|
+
const current = {
|
|
103
|
+
schemaVersion: 1,
|
|
104
|
+
revision: 0,
|
|
105
|
+
profile: { name: "Existing User" },
|
|
106
|
+
packages: [],
|
|
107
|
+
connections: [],
|
|
108
|
+
};
|
|
109
|
+
for (const stored of [
|
|
110
|
+
{ ...current, unknown: true },
|
|
111
|
+
{
|
|
112
|
+
...current,
|
|
113
|
+
packages: [
|
|
114
|
+
{
|
|
115
|
+
packageId: "settings",
|
|
116
|
+
version: "0.0.1",
|
|
117
|
+
state: "installed",
|
|
118
|
+
unknown: true,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
...current,
|
|
124
|
+
connections: [
|
|
125
|
+
{
|
|
126
|
+
connectionId: "connection-1",
|
|
127
|
+
packageId: "provider-ollama-cloud",
|
|
128
|
+
connectionTypeId: "ollama-cloud-account",
|
|
129
|
+
displayName: "Work",
|
|
130
|
+
state: "ready",
|
|
131
|
+
safeMetadata: {},
|
|
132
|
+
unknown: true,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
]) {
|
|
137
|
+
expect(() =>
|
|
138
|
+
decodeUserSettingsViewV1(migrateStoredUserSettingsV1(stored)),
|
|
139
|
+
).toThrow(ConfigurationDecodeError);
|
|
140
|
+
}
|
|
141
|
+
expect(migrateStoredUserSettingsV1(current)).toBe(current);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("migrates the pre-account-wide Bot record to an empty Package value bag", () => {
|
|
145
|
+
// Literal durable shape from eb0283edcce5daea976a21a9f6a6414bedc6e2bc,
|
|
146
|
+
// the first parent of PR #134's merge commit.
|
|
147
|
+
const stored = {
|
|
148
|
+
schemaVersion: 1,
|
|
149
|
+
botId: "primary",
|
|
150
|
+
revision: 4,
|
|
151
|
+
profile: { name: "Primary" },
|
|
152
|
+
notifications: { enabled: true },
|
|
153
|
+
assignments: [
|
|
154
|
+
{
|
|
155
|
+
assignmentId: "ollama-model",
|
|
156
|
+
packageId: "provider-ollama-cloud",
|
|
157
|
+
capabilityId: "ollama-cloud-models",
|
|
158
|
+
connectionId: "ollama-1",
|
|
159
|
+
state: "enabled",
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
assignmentOperations: [
|
|
163
|
+
{
|
|
164
|
+
commandId: "replace-model",
|
|
165
|
+
kind: "replacing",
|
|
166
|
+
assignmentId: "ollama-model",
|
|
167
|
+
state: "retrying",
|
|
168
|
+
target: {
|
|
169
|
+
assignmentId: "ollama-model",
|
|
170
|
+
packageId: "provider-ollama-cloud",
|
|
171
|
+
capabilityId: "ollama-cloud-models",
|
|
172
|
+
connectionId: "ollama-1",
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
model: {
|
|
177
|
+
connectionId: "ollama-1",
|
|
178
|
+
providerModelId: "glm-5.3-flash:cloud",
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
expect(decodeBotSettingsViewV1(migrateStoredBotSettingsV1(stored))).toEqual(
|
|
183
|
+
{
|
|
184
|
+
schemaVersion: 1,
|
|
185
|
+
botId: "primary",
|
|
186
|
+
revision: 4,
|
|
187
|
+
profile: { name: "Primary" },
|
|
188
|
+
notifications: { enabled: true },
|
|
189
|
+
packageValues: {},
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("keeps unknown Bot fields strict and current records unchanged", () => {
|
|
195
|
+
const current = initializeBotSettingsV1("primary");
|
|
196
|
+
expect(migrateStoredBotSettingsV1(current)).toBe(current);
|
|
197
|
+
const { packageValues: _packageValues, ...withoutPackageValues } = current;
|
|
198
|
+
expect(
|
|
199
|
+
decodeBotSettingsViewV1(
|
|
200
|
+
migrateStoredBotSettingsV1({
|
|
201
|
+
...withoutPackageValues,
|
|
202
|
+
modelAssignment: {
|
|
203
|
+
connectionId: "ollama-1",
|
|
204
|
+
providerModelId: "glm-5.3-flash:cloud",
|
|
205
|
+
},
|
|
206
|
+
}),
|
|
207
|
+
),
|
|
208
|
+
).toEqual(current);
|
|
209
|
+
expect(() =>
|
|
210
|
+
decodeBotSettingsViewV1(
|
|
211
|
+
migrateStoredBotSettingsV1({
|
|
212
|
+
...current,
|
|
213
|
+
assignments: [],
|
|
214
|
+
unknown: true,
|
|
215
|
+
}),
|
|
216
|
+
),
|
|
217
|
+
).toThrow(ConfigurationDecodeError);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
35
221
|
describe("shared public identifier policy", () => {
|
|
36
222
|
test("accepts the bounded cross-runtime identifier grammar", () => {
|
|
37
223
|
expect(isPublicIdentifier("Bot_1.release-candidate")).toBe(true);
|
package/src/index.ts
CHANGED
|
@@ -136,6 +136,8 @@ export interface PackageInstallationView {
|
|
|
136
136
|
catalogId?: string;
|
|
137
137
|
/** The immutable Catalog generation `catalogId` was read from. */
|
|
138
138
|
catalogGeneration?: string;
|
|
139
|
+
/** Exact non-first-party bundle admitted from the Catalog, when it carries code. */
|
|
140
|
+
contentHash?: string;
|
|
139
141
|
provenance?: PackageProvenanceV1;
|
|
140
142
|
/** The setup values the install carried, as GrokBot's `InstallPlugin{values}`. */
|
|
141
143
|
values?: Record<string, JsonValue | ModelBindingV1>;
|
|
@@ -145,9 +147,9 @@ export interface PackageInstallationView {
|
|
|
145
147
|
* A durable pending decision for the User: this Connection needs authorizing
|
|
146
148
|
* before it will do anything again.
|
|
147
149
|
*
|
|
148
|
-
* It carries **no URL**, and that is the whole design. A
|
|
149
|
-
*
|
|
150
|
-
* redirect is minted only by an authenticated User action. A single-use
|
|
150
|
+
* It carries **no URL**, and that is the whole design. A mount that meets a
|
|
151
|
+
* 401 may write one, but a Bot has no command that requests authorization and
|
|
152
|
+
* a redirect is minted only by an authenticated User action. A single-use
|
|
151
153
|
* ten-minute link stored in a projection every client reads, and replayed into
|
|
152
154
|
* every transcript, would outlive the decision it belonged to and would let a
|
|
153
155
|
* Bot hand its User a link it authored. The card is drawn from this record and
|
|
@@ -300,6 +302,7 @@ export type ConfigurationCommandV1 =
|
|
|
300
302
|
*/
|
|
301
303
|
catalogId?: string;
|
|
302
304
|
catalogGeneration?: string;
|
|
305
|
+
contentHash?: string;
|
|
303
306
|
values?: Record<string, JsonValue>;
|
|
304
307
|
})
|
|
305
308
|
| (CommandMetaV1 & {
|
|
@@ -1190,7 +1193,7 @@ export function decodeConfigurationCommandV1(
|
|
|
1190
1193
|
const command = exactCommand(
|
|
1191
1194
|
input,
|
|
1192
1195
|
["packageId", "version"],
|
|
1193
|
-
["catalogId", "catalogGeneration", "values", "enabled"],
|
|
1196
|
+
["catalogId", "catalogGeneration", "contentHash", "values", "enabled"],
|
|
1194
1197
|
);
|
|
1195
1198
|
// A Catalog install is all three of identity, generation and (optional)
|
|
1196
1199
|
// values or none of them: half a Catalog install would be an install
|
|
@@ -1208,6 +1211,14 @@ export function decodeConfigurationCommandV1(
|
|
|
1208
1211
|
"install values require a Catalog entry",
|
|
1209
1212
|
);
|
|
1210
1213
|
}
|
|
1214
|
+
if (
|
|
1215
|
+
command.catalogId === undefined &&
|
|
1216
|
+
command.contentHash !== undefined
|
|
1217
|
+
) {
|
|
1218
|
+
throw new ConfigurationDecodeError(
|
|
1219
|
+
"install contentHash requires a Catalog entry",
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1211
1222
|
if (
|
|
1212
1223
|
command.enabled !== undefined &&
|
|
1213
1224
|
typeof command.enabled !== "boolean"
|
|
@@ -1228,6 +1239,14 @@ export function decodeConfigurationCommandV1(
|
|
|
1228
1239
|
command.catalogGeneration,
|
|
1229
1240
|
"catalogGeneration",
|
|
1230
1241
|
),
|
|
1242
|
+
...(command.contentHash === undefined
|
|
1243
|
+
? {}
|
|
1244
|
+
: {
|
|
1245
|
+
contentHash: compositionHash(
|
|
1246
|
+
command.contentHash,
|
|
1247
|
+
"contentHash",
|
|
1248
|
+
),
|
|
1249
|
+
}),
|
|
1231
1250
|
}),
|
|
1232
1251
|
...(command.values === undefined
|
|
1233
1252
|
? {}
|
|
@@ -1614,7 +1633,14 @@ function packageInstallation(value: unknown): PackageInstallationView {
|
|
|
1614
1633
|
value,
|
|
1615
1634
|
"Package installation",
|
|
1616
1635
|
["packageId", "version", "state"],
|
|
1617
|
-
[
|
|
1636
|
+
[
|
|
1637
|
+
"failure",
|
|
1638
|
+
"catalogId",
|
|
1639
|
+
"catalogGeneration",
|
|
1640
|
+
"contentHash",
|
|
1641
|
+
"provenance",
|
|
1642
|
+
"values",
|
|
1643
|
+
],
|
|
1618
1644
|
);
|
|
1619
1645
|
if (
|
|
1620
1646
|
installation.state !== "installed" &&
|
|
@@ -1648,6 +1674,11 @@ function packageInstallation(value: unknown): PackageInstallationView {
|
|
|
1648
1674
|
"catalogGeneration",
|
|
1649
1675
|
),
|
|
1650
1676
|
}),
|
|
1677
|
+
...(installation.contentHash === undefined
|
|
1678
|
+
? {}
|
|
1679
|
+
: {
|
|
1680
|
+
contentHash: compositionHash(installation.contentHash, "contentHash"),
|
|
1681
|
+
}),
|
|
1651
1682
|
...(installation.provenance === undefined
|
|
1652
1683
|
? {}
|
|
1653
1684
|
: { provenance: installation.provenance }),
|
|
@@ -1777,6 +1808,115 @@ function schemaVersion(value: Record<string, unknown>): void {
|
|
|
1777
1808
|
}
|
|
1778
1809
|
}
|
|
1779
1810
|
|
|
1811
|
+
function storedPlainRecordV1(
|
|
1812
|
+
value: unknown,
|
|
1813
|
+
): Record<string, unknown> | undefined {
|
|
1814
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1815
|
+
return undefined;
|
|
1816
|
+
}
|
|
1817
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1818
|
+
return prototype === Object.prototype || prototype === null
|
|
1819
|
+
? (value as Record<string, unknown>)
|
|
1820
|
+
: undefined;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
function storedDataValueV1(
|
|
1824
|
+
value: Record<string, unknown>,
|
|
1825
|
+
key: string,
|
|
1826
|
+
): unknown {
|
|
1827
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1828
|
+
return descriptor && "value" in descriptor ? descriptor.value : undefined;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
function cloneStoredRecordV1(
|
|
1832
|
+
value: Record<string, unknown>,
|
|
1833
|
+
changes: Readonly<Record<string, unknown>>,
|
|
1834
|
+
removed: readonly string[] = [],
|
|
1835
|
+
): Record<string, unknown> {
|
|
1836
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1837
|
+
for (const key of removed) delete descriptors[key];
|
|
1838
|
+
for (const [key, next] of Object.entries(changes)) {
|
|
1839
|
+
const descriptor = descriptors[key];
|
|
1840
|
+
descriptors[key] =
|
|
1841
|
+
descriptor && "value" in descriptor
|
|
1842
|
+
? { ...descriptor, value: next }
|
|
1843
|
+
: {
|
|
1844
|
+
configurable: true,
|
|
1845
|
+
enumerable: true,
|
|
1846
|
+
value: next,
|
|
1847
|
+
writable: true,
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
return Object.create(Object.getPrototypeOf(value), descriptors) as Record<
|
|
1851
|
+
string,
|
|
1852
|
+
unknown
|
|
1853
|
+
>;
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
const PRE_ACCOUNT_WIDE_USER_FIELDS_V1 = [
|
|
1857
|
+
"newBotModelTemplate",
|
|
1858
|
+
"newBotModelTemplateSource",
|
|
1859
|
+
] as const;
|
|
1860
|
+
const PRE_ACCOUNT_WIDE_CONNECTION_METADATA_FIELDS_V1 = [
|
|
1861
|
+
"dependentAssignments",
|
|
1862
|
+
] as const;
|
|
1863
|
+
|
|
1864
|
+
/**
|
|
1865
|
+
* Migrates one raw User settings record across known durable shapes before the
|
|
1866
|
+
* current exact-field decoder sees it. Commit 1571b62 removed the model
|
|
1867
|
+
* template and commit d6730ad removed the Connection dependency ledger along
|
|
1868
|
+
* with the Assignment feature; migration drops those fields without
|
|
1869
|
+
* interpreting them.
|
|
1870
|
+
*/
|
|
1871
|
+
export function migrateStoredUserSettingsV1(stored: unknown): unknown {
|
|
1872
|
+
const settings = storedPlainRecordV1(stored);
|
|
1873
|
+
if (!settings || storedDataValueV1(settings, "schemaVersion") !== 1) {
|
|
1874
|
+
return stored;
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
let changed = false;
|
|
1878
|
+
const removedSettingsFields = PRE_ACCOUNT_WIDE_USER_FIELDS_V1.filter((key) =>
|
|
1879
|
+
Object.hasOwn(settings, key),
|
|
1880
|
+
);
|
|
1881
|
+
changed ||= removedSettingsFields.length > 0;
|
|
1882
|
+
|
|
1883
|
+
const storedConnections = storedDataValueV1(settings, "connections");
|
|
1884
|
+
let connections = storedConnections;
|
|
1885
|
+
if (Array.isArray(storedConnections)) {
|
|
1886
|
+
let connectionsChanged = false;
|
|
1887
|
+
const nextConnections = storedConnections.map((storedConnection) => {
|
|
1888
|
+
const connection = storedPlainRecordV1(storedConnection);
|
|
1889
|
+
if (!connection) return storedConnection;
|
|
1890
|
+
const storedMetadata = storedDataValueV1(connection, "safeMetadata");
|
|
1891
|
+
const metadata = storedPlainRecordV1(storedMetadata);
|
|
1892
|
+
if (!metadata) return storedConnection;
|
|
1893
|
+
const removedMetadataFields =
|
|
1894
|
+
PRE_ACCOUNT_WIDE_CONNECTION_METADATA_FIELDS_V1.filter((key) =>
|
|
1895
|
+
Object.hasOwn(metadata, key),
|
|
1896
|
+
);
|
|
1897
|
+
if (removedMetadataFields.length === 0) return storedConnection;
|
|
1898
|
+
connectionsChanged = true;
|
|
1899
|
+
const safeMetadata = cloneStoredRecordV1(
|
|
1900
|
+
metadata,
|
|
1901
|
+
{},
|
|
1902
|
+
removedMetadataFields,
|
|
1903
|
+
);
|
|
1904
|
+
return cloneStoredRecordV1(connection, { safeMetadata });
|
|
1905
|
+
});
|
|
1906
|
+
if (connectionsChanged) {
|
|
1907
|
+
changed = true;
|
|
1908
|
+
connections = nextConnections;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
if (!changed) return stored;
|
|
1913
|
+
return cloneStoredRecordV1(
|
|
1914
|
+
settings,
|
|
1915
|
+
connections === storedConnections ? {} : { connections },
|
|
1916
|
+
removedSettingsFields,
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1780
1920
|
export function decodeUserSettingsViewV1(input: unknown): UserSettingsViewV1 {
|
|
1781
1921
|
const value = exactRecord(
|
|
1782
1922
|
input,
|
|
@@ -1896,6 +2036,35 @@ export function decodeBotSettingsViewV1(input: unknown): BotSettingsViewV1 {
|
|
|
1896
2036
|
};
|
|
1897
2037
|
}
|
|
1898
2038
|
|
|
2039
|
+
const PRE_ACCOUNT_WIDE_BOT_FIELDS_V1 = [
|
|
2040
|
+
"assignments",
|
|
2041
|
+
"assignmentOperations",
|
|
2042
|
+
"model",
|
|
2043
|
+
// Some pre-release records used this name for the same removed feature.
|
|
2044
|
+
"modelAssignment",
|
|
2045
|
+
] as const;
|
|
2046
|
+
|
|
2047
|
+
/**
|
|
2048
|
+
* Migrates one raw Bot settings record across known durable shapes before the
|
|
2049
|
+
* current exact-field decoder sees it. The removed Assignment/model fields are
|
|
2050
|
+
* discarded; their presence identifies the shape that predates packageValues.
|
|
2051
|
+
*/
|
|
2052
|
+
export function migrateStoredBotSettingsV1(stored: unknown): unknown {
|
|
2053
|
+
const settings = storedPlainRecordV1(stored);
|
|
2054
|
+
if (!settings || storedDataValueV1(settings, "schemaVersion") !== 1) {
|
|
2055
|
+
return stored;
|
|
2056
|
+
}
|
|
2057
|
+
const removed = PRE_ACCOUNT_WIDE_BOT_FIELDS_V1.filter((key) =>
|
|
2058
|
+
Object.hasOwn(settings, key),
|
|
2059
|
+
);
|
|
2060
|
+
if (removed.length === 0) return stored;
|
|
2061
|
+
return cloneStoredRecordV1(
|
|
2062
|
+
settings,
|
|
2063
|
+
Object.hasOwn(settings, "packageValues") ? {} : { packageValues: {} },
|
|
2064
|
+
removed,
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2067
|
+
|
|
1899
2068
|
export function decodeConfigurationViewV1(input: unknown): ConfigurationViewV1 {
|
|
1900
2069
|
const value = record(input, "configuration");
|
|
1901
2070
|
return "botId" in value
|
|
@@ -1957,6 +2126,11 @@ const COMPOSITION_GENERATION_STATUSES_V1: readonly CompositionGenerationStatusVi
|
|
|
1957
2126
|
|
|
1958
2127
|
export type CompositionProvenanceViewV1 =
|
|
1959
2128
|
| { kind: "first-party" }
|
|
2129
|
+
| {
|
|
2130
|
+
kind: "catalog";
|
|
2131
|
+
catalogId: string;
|
|
2132
|
+
catalogGeneration: string;
|
|
2133
|
+
}
|
|
1960
2134
|
| { kind: "user"; userId: string; authoredAt: string }
|
|
1961
2135
|
| {
|
|
1962
2136
|
kind: "bot";
|
|
@@ -1971,7 +2145,14 @@ export type CompositionOriginViewV1 =
|
|
|
1971
2145
|
| { kind: "bootstrap" }
|
|
1972
2146
|
| { kind: "bot-authored"; runId: string; sessionId: string; turnId: string }
|
|
1973
2147
|
| { kind: "user-install"; userId: string }
|
|
1974
|
-
| { kind: "revert"; revertsTo: string; userId: string }
|
|
2148
|
+
| { kind: "revert"; revertsTo: string; userId: string }
|
|
2149
|
+
| {
|
|
2150
|
+
kind: "revert";
|
|
2151
|
+
revertsTo: string;
|
|
2152
|
+
botId: string;
|
|
2153
|
+
runId: string;
|
|
2154
|
+
turnId: string;
|
|
2155
|
+
};
|
|
1975
2156
|
|
|
1976
2157
|
export interface CompositionMemberViewV1 {
|
|
1977
2158
|
packageId: string;
|
|
@@ -2109,6 +2290,25 @@ function compositionProvenanceView(
|
|
|
2109
2290
|
exactRecord(input, "Composition provenance", ["kind"]);
|
|
2110
2291
|
return { kind: "first-party" };
|
|
2111
2292
|
}
|
|
2293
|
+
if (kind === "catalog") {
|
|
2294
|
+
const value = exactRecord(input, "Composition provenance", [
|
|
2295
|
+
"kind",
|
|
2296
|
+
"catalogId",
|
|
2297
|
+
"catalogGeneration",
|
|
2298
|
+
]);
|
|
2299
|
+
return {
|
|
2300
|
+
kind: "catalog",
|
|
2301
|
+
catalogId: identifier(
|
|
2302
|
+
value.catalogId,
|
|
2303
|
+
"Composition provenance catalogId",
|
|
2304
|
+
),
|
|
2305
|
+
catalogGeneration: text(
|
|
2306
|
+
value.catalogGeneration,
|
|
2307
|
+
"Composition provenance catalogGeneration",
|
|
2308
|
+
256,
|
|
2309
|
+
),
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2112
2312
|
if (kind === "user") {
|
|
2113
2313
|
const value = exactRecord(input, "Composition provenance", [
|
|
2114
2314
|
"kind",
|
|
@@ -2176,18 +2376,32 @@ function compositionOriginView(input: unknown): CompositionOriginViewV1 {
|
|
|
2176
2376
|
};
|
|
2177
2377
|
}
|
|
2178
2378
|
if (kind === "revert") {
|
|
2179
|
-
const value =
|
|
2379
|
+
const value = record(input, "Composition origin");
|
|
2380
|
+
const revertsTo = decodeCompositionGenerationIdV1(
|
|
2381
|
+
value.revertsTo,
|
|
2382
|
+
"Composition origin revertsTo",
|
|
2383
|
+
);
|
|
2384
|
+
if (Object.hasOwn(value, "userId")) {
|
|
2385
|
+
exactRecord(input, "Composition origin", ["kind", "revertsTo", "userId"]);
|
|
2386
|
+
return {
|
|
2387
|
+
kind: "revert",
|
|
2388
|
+
revertsTo,
|
|
2389
|
+
userId: text(value.userId, "Composition origin userId", 256),
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
const bot = exactRecord(input, "Composition origin", [
|
|
2180
2393
|
"kind",
|
|
2181
2394
|
"revertsTo",
|
|
2182
|
-
"
|
|
2395
|
+
"botId",
|
|
2396
|
+
"runId",
|
|
2397
|
+
"turnId",
|
|
2183
2398
|
]);
|
|
2184
2399
|
return {
|
|
2185
2400
|
kind: "revert",
|
|
2186
|
-
revertsTo
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
),
|
|
2190
|
-
userId: text(value.userId, "Composition origin userId", 256),
|
|
2401
|
+
revertsTo,
|
|
2402
|
+
botId: decodeBotIdV1(bot.botId),
|
|
2403
|
+
runId: text(bot.runId, "Composition origin runId", 128),
|
|
2404
|
+
turnId: text(bot.turnId, "Composition origin turnId", 128),
|
|
2191
2405
|
};
|
|
2192
2406
|
}
|
|
2193
2407
|
throw new ConfigurationDecodeError("Composition origin kind is invalid");
|