@sellable/install 0.1.656-wip.phase189.20260815.1 → 0.1.656
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/container/Dockerfile +4 -4
- package/container/README.md +1 -1
- package/lib/sellable-agent/default-profile-bundle.mjs +1 -1
- package/lib/sellable-agent/default-profile-reconciler.mjs +103 -317
- package/lib/sellable-agent/fly-admin-image/Dockerfile +1 -1
- package/lib/sellable-agent/fly-admin-image/admin-runtime.mjs +15 -5
- package/lib/sellable-agent/fly-customer-image/Dockerfile +1 -1
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +1 -1
- package/lib/sellable-agent/host-bootstrap.mjs +1 -1
- package/lib/sellable-agent/host-worker.mjs +1 -1
- package/lib/sellable-agent/profile-materializer.mjs +1 -1
- package/lib/sellable-agent/provisioning-adapter.mjs +1 -1
- package/package.json +1 -1
package/container/Dockerfile
CHANGED
|
@@ -3,12 +3,12 @@ FROM ${HERMES_IMAGE}
|
|
|
3
3
|
|
|
4
4
|
USER root
|
|
5
5
|
|
|
6
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.656
|
|
6
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.656
|
|
7
7
|
ARG MCP_PACKAGE=@sellable/mcp@0.1.880
|
|
8
8
|
ARG INSTALLER_INTEGRITY
|
|
9
9
|
ARG MCP_INTEGRITY=sha512-+Fo8aQlYduLaQMQff+ZA25LeUhWZ7I7UH5DECpND84LQ+rHSjKeKFj4Zs93bVHXwQCIy2OLbr75y4f4+CyT57w==
|
|
10
10
|
|
|
11
|
-
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.656
|
|
11
|
+
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.656" \
|
|
12
12
|
&& test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.880" \
|
|
13
13
|
&& test -n "${INSTALLER_INTEGRITY}" \
|
|
14
14
|
&& test "$(npm view "${INSTALLER_PACKAGE}" dist.integrity)" = "${INSTALLER_INTEGRITY}" \
|
|
@@ -23,9 +23,9 @@ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.656-wip.phase189.202608
|
|
|
23
23
|
&& mkdir -p /opt/sellable-agent/install-root /opt/sellable-agent/mcp-root \
|
|
24
24
|
&& npm install --prefix /opt/sellable-agent/install-root --omit=dev --ignore-scripts --no-audit --no-fund "${INSTALLER_PACKAGE}" \
|
|
25
25
|
&& npm install --prefix /opt/sellable-agent/mcp-root --omit=dev --ignore-scripts --no-audit --no-fund "${MCP_PACKAGE}" \
|
|
26
|
-
&& node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.656
|
|
26
|
+
&& node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.656' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.880') throw new Error('package identity rejected');" \
|
|
27
27
|
&& node --input-type=module -e "import { buildExternalRuntimeClosure } from '/opt/sellable-agent/install-root/node_modules/@sellable/install/lib/sellable-agent/external-runtime-builder.mjs'; const built = buildExternalRuntimeClosure({ mcpPackageRoot: '/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp', mcpNodeModulesRoot: '/opt/sellable-agent/mcp-root/node_modules', hermesSourceRoot: '/opt/hermes', ownerUid: 0, ownerGid: 0 }); if (!built.closureDigest) throw new Error('runtime closure rejected');" \
|
|
28
|
-
&& node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.656
|
|
28
|
+
&& node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.656', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.880', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
|
|
29
29
|
&& npm cache clean --force
|
|
30
30
|
|
|
31
31
|
COPY entrypoint.sh /usr/local/bin/sellable-agent-container-entrypoint
|
package/container/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
This image is a dedicated Agent worker/runtime boundary beside an existing Hermes dashboard. It never replaces or reconfigures the dashboard container.
|
|
4
4
|
|
|
5
5
|
Build from this directory after
|
|
6
|
-
`@sellable/install@0.1.656
|
|
6
|
+
`@sellable/install@0.1.656` is
|
|
7
7
|
published:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
@@ -193,7 +193,7 @@ function writeBundle(outputRoot, result) {
|
|
|
193
193
|
chmodSync(join(outputRoot, "manifest.json"), 0o444);
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
/** Build the one canonical kind bundle.
|
|
196
|
+
/** Build the one canonical kind bundle. Its compiled digest is the image pin. */
|
|
197
197
|
export function buildDefaultProfileBundle({
|
|
198
198
|
kind,
|
|
199
199
|
fragmentPaths,
|
|
@@ -1,30 +1,16 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import {
|
|
3
|
-
chmodSync,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
existsSync,
|
|
7
|
-
fstatSync,
|
|
8
|
-
fsyncSync,
|
|
9
|
-
lstatSync,
|
|
10
|
-
mkdirSync,
|
|
11
|
-
openSync,
|
|
12
|
-
readFileSync,
|
|
13
|
-
readdirSync,
|
|
14
|
-
realpathSync,
|
|
15
|
-
renameSync,
|
|
16
|
-
rmSync,
|
|
17
|
-
writeFileSync,
|
|
3
|
+
chmodSync, closeSync, constants, existsSync, fstatSync, fsyncSync, lstatSync,
|
|
4
|
+
mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync,
|
|
5
|
+
rmSync, writeFileSync,
|
|
18
6
|
} from "node:fs";
|
|
19
7
|
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
20
8
|
|
|
21
9
|
import { withProfileSoulLock } from "./fly-soul-bridge.mjs";
|
|
22
10
|
import { ensureHermesNamedCron } from "./hermes-memory-snapshot.mjs";
|
|
23
11
|
|
|
24
|
-
export const DEFAULT_PROFILE_RECONCILE_RECEIPT =
|
|
25
|
-
|
|
26
|
-
export const DEFAULT_PROFILE_RECONCILE_SCHEMA =
|
|
27
|
-
"sellable-agent-default-profile-reconcile/v1";
|
|
12
|
+
export const DEFAULT_PROFILE_RECONCILE_RECEIPT = ".sellable-agent/default-profile-reconcile.json";
|
|
13
|
+
export const DEFAULT_PROFILE_RECONCILE_SCHEMA = "sellable-agent-default-profile-reconcile/v1";
|
|
28
14
|
|
|
29
15
|
const BUNDLE_SCHEMA = "sellable-agent-default-profile-bundle/v1";
|
|
30
16
|
const ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
@@ -33,16 +19,12 @@ const SHA256 = /^[a-f0-9]{64}$/;
|
|
|
33
19
|
const FENCE = /^[1-9][0-9]{0,19}$/;
|
|
34
20
|
const STAGE_PREFIX = ".sellable-default-stage-";
|
|
35
21
|
const MAX_BYTES = 1024 * 1024;
|
|
36
|
-
const ADMIN_MATERIALIZER_ANCHOR = "skills/sellable-admin/SKILL.md";
|
|
37
|
-
const ADMIN_NAMESPACE_RECEIPT_SCHEMA =
|
|
38
|
-
"sellable-admin-skill-namespace-receipt/v1";
|
|
39
22
|
|
|
40
23
|
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
41
24
|
const canonicalJson = (value) => {
|
|
42
25
|
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
43
26
|
if (value && typeof value === "object") {
|
|
44
|
-
return `{${Object.keys(value)
|
|
45
|
-
.sort()
|
|
27
|
+
return `{${Object.keys(value).sort()
|
|
46
28
|
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
47
29
|
.join(",")}}`;
|
|
48
30
|
}
|
|
@@ -69,7 +51,8 @@ export function buildDefaultProfileProjection({
|
|
|
69
51
|
bundleVersion: Number(
|
|
70
52
|
env.SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_VERSION ?? ""
|
|
71
53
|
),
|
|
72
|
-
bundleDigest:
|
|
54
|
+
bundleDigest:
|
|
55
|
+
env.SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_DIGEST ?? "",
|
|
73
56
|
};
|
|
74
57
|
if (
|
|
75
58
|
!["CUSTOMER", "ADMIN"].includes(kind) ||
|
|
@@ -88,14 +71,14 @@ export function buildDefaultProfileProjection({
|
|
|
88
71
|
) {
|
|
89
72
|
throw new Error("default_profile_bundle_identity_rejected");
|
|
90
73
|
}
|
|
91
|
-
const verifiedFixture =
|
|
92
|
-
fixtureTarget ??
|
|
74
|
+
const verifiedFixture = fixtureTarget ??
|
|
93
75
|
(env.SELLABLE_AGENT_DEFAULT_PROFILE_FIXTURE_TEST_ONLY === "1"
|
|
94
76
|
? {
|
|
95
77
|
testOnly: true,
|
|
96
78
|
workspaceId:
|
|
97
79
|
env.SELLABLE_AGENT_DEFAULT_PROFILE_FIXTURE_WORKSPACE_ID ?? "",
|
|
98
|
-
agentId:
|
|
80
|
+
agentId:
|
|
81
|
+
env.SELLABLE_AGENT_DEFAULT_PROFILE_FIXTURE_AGENT_ID ?? "",
|
|
99
82
|
}
|
|
100
83
|
: undefined);
|
|
101
84
|
const exactFixture =
|
|
@@ -132,7 +115,11 @@ export function bindDefaultProfileProbeCron({
|
|
|
132
115
|
) {
|
|
133
116
|
throw new Error("default_profile_cron_binding_rejected");
|
|
134
117
|
}
|
|
135
|
-
const scriptPath = join(
|
|
118
|
+
const scriptPath = join(
|
|
119
|
+
profileRoot,
|
|
120
|
+
"scripts",
|
|
121
|
+
"sellable-default-probe.sh"
|
|
122
|
+
);
|
|
136
123
|
const binding = {
|
|
137
124
|
operationId: activeProjection.operationId,
|
|
138
125
|
bootId,
|
|
@@ -169,23 +156,13 @@ PY
|
|
|
169
156
|
|
|
170
157
|
function readRegular(path, maxBytes = MAX_BYTES) {
|
|
171
158
|
const link = lstatSync(path);
|
|
172
|
-
if (
|
|
173
|
-
link.isSymbolicLink() ||
|
|
174
|
-
!link.isFile() ||
|
|
175
|
-
link.nlink !== 1 ||
|
|
176
|
-
link.size > maxBytes
|
|
177
|
-
)
|
|
159
|
+
if (link.isSymbolicLink() || !link.isFile() || link.nlink !== 1 || link.size > maxBytes)
|
|
178
160
|
throw new Error("default_profile_file_rejected");
|
|
179
161
|
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
180
162
|
try {
|
|
181
163
|
const opened = fstatSync(fd);
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
opened.dev !== link.dev ||
|
|
185
|
-
opened.ino !== link.ino ||
|
|
186
|
-
opened.nlink !== 1 ||
|
|
187
|
-
opened.size !== link.size
|
|
188
|
-
) {
|
|
164
|
+
if (!opened.isFile() || opened.dev !== link.dev || opened.ino !== link.ino ||
|
|
165
|
+
opened.nlink !== 1 || opened.size !== link.size) {
|
|
189
166
|
throw new Error("default_profile_file_rejected");
|
|
190
167
|
}
|
|
191
168
|
return readFileSync(fd);
|
|
@@ -194,123 +171,9 @@ function readRegular(path, maxBytes = MAX_BYTES) {
|
|
|
194
171
|
}
|
|
195
172
|
}
|
|
196
173
|
|
|
197
|
-
function adminSkillNamespaceReceiptHash(profileRoot) {
|
|
198
|
-
const namespaceRoot = join(profileRoot, "skills", "sellable-admin");
|
|
199
|
-
if (
|
|
200
|
-
!existsSync(namespaceRoot) ||
|
|
201
|
-
lstatSync(namespaceRoot).isSymbolicLink() ||
|
|
202
|
-
!lstatSync(namespaceRoot).isDirectory() ||
|
|
203
|
-
realpathSync(namespaceRoot) !== namespaceRoot
|
|
204
|
-
) {
|
|
205
|
-
throw new Error("default_profile_admin_skill_namespace_rejected");
|
|
206
|
-
}
|
|
207
|
-
const names = readdirSync(namespaceRoot).sort();
|
|
208
|
-
if (names.length < 1 || names.length > 64) {
|
|
209
|
-
throw new Error("default_profile_admin_skill_namespace_rejected");
|
|
210
|
-
}
|
|
211
|
-
let totalBytes = 0;
|
|
212
|
-
const entries = names.map((name) => {
|
|
213
|
-
if (!MANAGED_ID.test(name)) {
|
|
214
|
-
throw new Error("default_profile_admin_skill_namespace_rejected");
|
|
215
|
-
}
|
|
216
|
-
const directory = join(namespaceRoot, name);
|
|
217
|
-
if (
|
|
218
|
-
lstatSync(directory).isSymbolicLink() ||
|
|
219
|
-
!lstatSync(directory).isDirectory() ||
|
|
220
|
-
realpathSync(directory) !== directory
|
|
221
|
-
) {
|
|
222
|
-
throw new Error("default_profile_admin_skill_namespace_rejected");
|
|
223
|
-
}
|
|
224
|
-
const relativePath = `skills/sellable-admin/${name}/SKILL.md`;
|
|
225
|
-
const content = readRegular(join(directory, "SKILL.md"), 64 * 1024);
|
|
226
|
-
totalBytes += content.byteLength;
|
|
227
|
-
if (totalBytes > MAX_BYTES) {
|
|
228
|
-
throw new Error("default_profile_admin_skill_namespace_rejected");
|
|
229
|
-
}
|
|
230
|
-
return { path: relativePath, sha256: sha256(content) };
|
|
231
|
-
});
|
|
232
|
-
return sha256(
|
|
233
|
-
canonicalJson({
|
|
234
|
-
schemaVersion: ADMIN_NAMESPACE_RECEIPT_SCHEMA,
|
|
235
|
-
entries,
|
|
236
|
-
})
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function materializerReceiptHash(profileRoot, path) {
|
|
241
|
-
const target = join(profileRoot, ...path.split("/"));
|
|
242
|
-
if (path === ADMIN_MATERIALIZER_ANCHOR && !existsSync(target)) {
|
|
243
|
-
return adminSkillNamespaceReceiptHash(profileRoot);
|
|
244
|
-
}
|
|
245
|
-
return sha256(readRegular(target, 64 * 1024));
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export function buildDefaultProfileMaterializerReceipt({
|
|
249
|
-
profileId,
|
|
250
|
-
profileRoot,
|
|
251
|
-
materializerReceipts,
|
|
252
|
-
} = {}) {
|
|
253
|
-
const allowed = new Set([
|
|
254
|
-
"SOUL.md",
|
|
255
|
-
"skills/sellable/SKILL.md",
|
|
256
|
-
ADMIN_MATERIALIZER_ANCHOR,
|
|
257
|
-
]);
|
|
258
|
-
if (
|
|
259
|
-
!ID.test(profileId ?? "") ||
|
|
260
|
-
!isAbsolute(profileRoot ?? "") ||
|
|
261
|
-
!Array.isArray(materializerReceipts) ||
|
|
262
|
-
materializerReceipts.length < 1 ||
|
|
263
|
-
materializerReceipts.length > allowed.size ||
|
|
264
|
-
new Set(materializerReceipts).size !== materializerReceipts.length ||
|
|
265
|
-
materializerReceipts.some((path) => !allowed.has(path))
|
|
266
|
-
) {
|
|
267
|
-
throw new Error("default_profile_materializer_receipt_rejected");
|
|
268
|
-
}
|
|
269
|
-
return {
|
|
270
|
-
profileId,
|
|
271
|
-
fileHashes: Object.fromEntries(
|
|
272
|
-
materializerReceipts.map((path) => [
|
|
273
|
-
path,
|
|
274
|
-
materializerReceiptHash(profileRoot, path),
|
|
275
|
-
])
|
|
276
|
-
),
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
export function verifyDefaultProfileMaterializerReceipt({
|
|
281
|
-
profileRoot,
|
|
282
|
-
materializerReceipts,
|
|
283
|
-
materializerReceipt,
|
|
284
|
-
} = {}) {
|
|
285
|
-
if (
|
|
286
|
-
!ID.test(materializerReceipt?.profileId ?? "") ||
|
|
287
|
-
!materializerReceipt?.fileHashes ||
|
|
288
|
-
Array.isArray(materializerReceipt.fileHashes)
|
|
289
|
-
) {
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
try {
|
|
293
|
-
const current = buildDefaultProfileMaterializerReceipt({
|
|
294
|
-
profileId: materializerReceipt.profileId,
|
|
295
|
-
profileRoot,
|
|
296
|
-
materializerReceipts,
|
|
297
|
-
});
|
|
298
|
-
return materializerReceipts.every(
|
|
299
|
-
(path) =>
|
|
300
|
-
SHA256.test(materializerReceipt.fileHashes[path] ?? "") &&
|
|
301
|
-
materializerReceipt.fileHashes[path] === current.fileHashes[path]
|
|
302
|
-
);
|
|
303
|
-
} catch {
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
174
|
function atomicWrite(path, bytes, mode) {
|
|
309
175
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
310
|
-
const stage = join(
|
|
311
|
-
dirname(path),
|
|
312
|
-
`${STAGE_PREFIX}${basename(path)}-${process.pid}-${randomUUID()}`
|
|
313
|
-
);
|
|
176
|
+
const stage = join(dirname(path), `${STAGE_PREFIX}${basename(path)}-${process.pid}-${randomUUID()}`);
|
|
314
177
|
writeFileSync(stage, bytes, { flag: "wx", mode });
|
|
315
178
|
chmodSync(stage, mode);
|
|
316
179
|
const fd = openSync(stage, constants.O_RDONLY);
|
|
@@ -323,23 +186,16 @@ function atomicWrite(path, bytes, mode) {
|
|
|
323
186
|
}
|
|
324
187
|
|
|
325
188
|
function readBundle(bundleRoot) {
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
lstatSync(bundleRoot).isSymbolicLink() ||
|
|
330
|
-
!lstatSync(bundleRoot).isDirectory() ||
|
|
331
|
-
realpathSync(bundleRoot) !== bundleRoot
|
|
332
|
-
) {
|
|
189
|
+
if (!isAbsolute(bundleRoot ?? "") || !existsSync(bundleRoot) ||
|
|
190
|
+
lstatSync(bundleRoot).isSymbolicLink() || !lstatSync(bundleRoot).isDirectory() ||
|
|
191
|
+
realpathSync(bundleRoot) !== bundleRoot) {
|
|
333
192
|
throw new Error("default_profile_bundle_rejected");
|
|
334
193
|
}
|
|
335
|
-
const manifest = JSON.parse(
|
|
336
|
-
readRegular(join(bundleRoot, "manifest.json")).toString("utf8")
|
|
337
|
-
);
|
|
194
|
+
const manifest = JSON.parse(readRegular(join(bundleRoot, "manifest.json")).toString("utf8"));
|
|
338
195
|
if (
|
|
339
196
|
manifest?.schemaVersion !== BUNDLE_SCHEMA ||
|
|
340
197
|
!["CUSTOMER", "ADMIN"].includes(manifest.kind) ||
|
|
341
|
-
!Number.isSafeInteger(manifest.bundleVersion) ||
|
|
342
|
-
manifest.bundleVersion < 1 ||
|
|
198
|
+
!Number.isSafeInteger(manifest.bundleVersion) || manifest.bundleVersion < 1 ||
|
|
343
199
|
!SHA256.test(manifest.bundleDigest ?? "") ||
|
|
344
200
|
!Array.isArray(manifest.materializerReceipts) ||
|
|
345
201
|
!Array.isArray(manifest.files)
|
|
@@ -347,10 +203,9 @@ function readBundle(bundleRoot) {
|
|
|
347
203
|
throw new Error("default_profile_bundle_rejected");
|
|
348
204
|
}
|
|
349
205
|
const files = manifest.files.map((entry) => {
|
|
350
|
-
const expectedPath =
|
|
351
|
-
entry
|
|
352
|
-
|
|
353
|
-
: `crons/${entry?.name}.json`;
|
|
206
|
+
const expectedPath = entry?.type === "skill"
|
|
207
|
+
? `skills/sellable-defaults/${entry.id}/SKILL.md`
|
|
208
|
+
: `crons/${entry?.name}.json`;
|
|
354
209
|
if (
|
|
355
210
|
!MANAGED_ID.test(entry?.id ?? "") ||
|
|
356
211
|
!["skill", "cron"].includes(entry.type) ||
|
|
@@ -359,47 +214,37 @@ function readBundle(bundleRoot) {
|
|
|
359
214
|
entry.mode !== "0444" ||
|
|
360
215
|
entry.path !== expectedPath ||
|
|
361
216
|
!SHA256.test(entry.sha256 ?? "") ||
|
|
362
|
-
(entry.type === "cron" &&
|
|
363
|
-
|
|
364
|
-
!entry.name.startsWith("sellable-default-")))
|
|
217
|
+
(entry.type === "cron" && (!MANAGED_ID.test(entry.name ?? "") ||
|
|
218
|
+
!entry.name.startsWith("sellable-default-")))
|
|
365
219
|
) {
|
|
366
220
|
throw new Error("default_profile_bundle_rejected");
|
|
367
221
|
}
|
|
368
|
-
const content = readRegular(
|
|
369
|
-
join(bundleRoot, ...entry.path.split("/")),
|
|
370
|
-
64 * 1024
|
|
371
|
-
);
|
|
222
|
+
const content = readRegular(join(bundleRoot, ...entry.path.split("/")), 64 * 1024);
|
|
372
223
|
if (sha256(content) !== entry.sha256)
|
|
373
224
|
throw new Error("default_profile_bundle_rejected");
|
|
374
225
|
return { ...entry, content: content.toString("utf8") };
|
|
375
226
|
});
|
|
376
227
|
const { bundleDigest, ...unsignedManifest } = manifest;
|
|
377
|
-
if (
|
|
378
|
-
sha256(canonicalJson({ manifest: unsignedManifest, files })) !==
|
|
379
|
-
bundleDigest
|
|
380
|
-
)
|
|
228
|
+
if (sha256(canonicalJson({ manifest: unsignedManifest, files })) !== bundleDigest)
|
|
381
229
|
throw new Error("default_profile_bundle_rejected");
|
|
382
230
|
return { manifest, files };
|
|
383
231
|
}
|
|
384
232
|
|
|
385
233
|
function activeEntries(bundle, projection) {
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
bundle.manifest.bundleDigest !== projection.bundleDigest
|
|
390
|
-
) {
|
|
234
|
+
if (bundle.manifest.kind !== projection.kind ||
|
|
235
|
+
bundle.manifest.bundleVersion !== projection.bundleVersion ||
|
|
236
|
+
bundle.manifest.bundleDigest !== projection.bundleDigest) {
|
|
391
237
|
throw new Error("default_profile_projection_mismatch");
|
|
392
238
|
}
|
|
393
239
|
const target = projection.fixtureTarget;
|
|
394
|
-
const fixtureAllowed =
|
|
395
|
-
target?.testOnly === true &&
|
|
240
|
+
const fixtureAllowed = target?.testOnly === true &&
|
|
396
241
|
target.workspaceId === projection.workspaceId &&
|
|
397
242
|
target.agentId === projection.agentId;
|
|
398
243
|
return bundle.files
|
|
399
244
|
.filter((entry) => entry.kind !== "FIXTURE" || fixtureAllowed)
|
|
400
245
|
.sort(
|
|
401
|
-
(left, right) =>
|
|
402
|
-
(
|
|
246
|
+
(left, right) => (left.type === "skill" ? 0 : 1) -
|
|
247
|
+
(right.type === "skill" ? 0 : 1) ||
|
|
403
248
|
left.path.localeCompare(right.path)
|
|
404
249
|
);
|
|
405
250
|
}
|
|
@@ -408,8 +253,8 @@ function validateProjection(value) {
|
|
|
408
253
|
return Boolean(
|
|
409
254
|
value &&
|
|
410
255
|
["CUSTOMER", "ADMIN"].includes(value.kind) &&
|
|
411
|
-
["profileId", "workspaceId", "agentId", "operationId"].every(
|
|
412
|
-
ID.test(value[key] ?? "")
|
|
256
|
+
["profileId", "workspaceId", "agentId", "operationId"].every(
|
|
257
|
+
(key) => ID.test(value[key] ?? "")
|
|
413
258
|
) &&
|
|
414
259
|
Number.isSafeInteger(value.authorityGeneration) &&
|
|
415
260
|
value.authorityGeneration > 0 &&
|
|
@@ -460,12 +305,8 @@ export async function reconcileDefaultProfileBundle({
|
|
|
460
305
|
return { ok: true, status: "EMPTY", changed: false, actions: [] };
|
|
461
306
|
if (!validateProjection(activeProjection))
|
|
462
307
|
return refused("default_profile_projection_rejected");
|
|
463
|
-
if (
|
|
464
|
-
|
|
465
|
-
!isAbsolute(profileRoot ?? "") ||
|
|
466
|
-
typeof withProfileLock !== "function" ||
|
|
467
|
-
typeof reattestAuthority !== "function"
|
|
468
|
-
) {
|
|
308
|
+
if (!isAbsolute(dataRoot ?? "") || !isAbsolute(profileRoot ?? "") ||
|
|
309
|
+
typeof withProfileLock !== "function" || typeof reattestAuthority !== "function") {
|
|
469
310
|
return refused("default_profile_reconcile_input_rejected");
|
|
470
311
|
}
|
|
471
312
|
|
|
@@ -476,62 +317,48 @@ export async function reconcileDefaultProfileBundle({
|
|
|
476
317
|
activeProjection.profileId,
|
|
477
318
|
async () => {
|
|
478
319
|
const authority = await reattestAuthority(activeProjection);
|
|
479
|
-
if (
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
activeProjection.authorityGeneration ||
|
|
483
|
-
authority?.fence !== activeProjection.fence
|
|
484
|
-
) {
|
|
320
|
+
if (authority?.operationId !== activeProjection.operationId ||
|
|
321
|
+
authority?.authorityGeneration !== activeProjection.authorityGeneration ||
|
|
322
|
+
authority?.fence !== activeProjection.fence) {
|
|
485
323
|
return refused("default_profile_authority_stale");
|
|
486
324
|
}
|
|
487
|
-
if (
|
|
488
|
-
|
|
489
|
-
lstatSync(profileRoot).isSymbolicLink() ||
|
|
490
|
-
!lstatSync(profileRoot).isDirectory() ||
|
|
491
|
-
realpathSync(profileRoot) !== profileRoot
|
|
492
|
-
) {
|
|
325
|
+
if (!existsSync(profileRoot) || lstatSync(profileRoot).isSymbolicLink() ||
|
|
326
|
+
!lstatSync(profileRoot).isDirectory() || realpathSync(profileRoot) !== profileRoot) {
|
|
493
327
|
throw new Error("default_profile_root_rejected");
|
|
494
328
|
}
|
|
495
329
|
|
|
496
330
|
const bundle = readBundle(bundleRoot);
|
|
497
331
|
const entries = activeEntries(bundle, activeProjection);
|
|
498
|
-
if (
|
|
499
|
-
|
|
500
|
-
!verifyDefaultProfileMaterializerReceipt({
|
|
501
|
-
profileRoot,
|
|
502
|
-
materializerReceipts: bundle.manifest.materializerReceipts,
|
|
503
|
-
materializerReceipt,
|
|
504
|
-
})
|
|
505
|
-
) {
|
|
332
|
+
if (materializerReceipt?.profileId !== activeProjection.profileId ||
|
|
333
|
+
!materializerReceipt.fileHashes || Array.isArray(materializerReceipt.fileHashes)) {
|
|
506
334
|
throw new Error("default_profile_materializer_receipt_rejected");
|
|
507
335
|
}
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
336
|
+
for (const path of bundle.manifest.materializerReceipts) {
|
|
337
|
+
if (
|
|
338
|
+
!["SOUL.md", "skills/sellable/SKILL.md", "skills/sellable-admin/SKILL.md"]
|
|
339
|
+
.includes(path) ||
|
|
340
|
+
!SHA256.test(materializerReceipt.fileHashes[path] ?? "") ||
|
|
341
|
+
sha256(readRegular(join(profileRoot, ...path.split("/")), 64 * 1024)) !==
|
|
342
|
+
materializerReceipt.fileHashes[path]
|
|
343
|
+
) {
|
|
344
|
+
throw new Error("default_profile_materializer_receipt_rejected");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const materializerReceiptSha256 = sha256(canonicalJson(materializerReceipt));
|
|
511
348
|
|
|
512
|
-
const receiptPath = join(
|
|
513
|
-
profileRoot,
|
|
514
|
-
DEFAULT_PROFILE_RECONCILE_RECEIPT
|
|
515
|
-
);
|
|
349
|
+
const receiptPath = join(profileRoot, DEFAULT_PROFILE_RECONCILE_RECEIPT);
|
|
516
350
|
let prior = null;
|
|
517
351
|
if (existsSync(receiptPath)) {
|
|
518
352
|
prior = JSON.parse(readRegular(receiptPath).toString("utf8"));
|
|
519
|
-
if (
|
|
520
|
-
|
|
521
|
-
prior.profileId !== activeProjection.profileId ||
|
|
522
|
-
!Array.isArray(prior.entries)
|
|
523
|
-
) {
|
|
353
|
+
if (prior?.schemaVersion !== DEFAULT_PROFILE_RECONCILE_SCHEMA ||
|
|
354
|
+
prior.profileId !== activeProjection.profileId || !Array.isArray(prior.entries)) {
|
|
524
355
|
throw new Error("default_profile_receipt_rejected");
|
|
525
356
|
}
|
|
526
357
|
}
|
|
527
358
|
const historical = (entry) =>
|
|
528
|
-
prior?.entries.find(
|
|
529
|
-
(value)
|
|
530
|
-
|
|
531
|
-
value.id === entry.id &&
|
|
532
|
-
(entry.type !== "cron" || value.name === entry.name) &&
|
|
533
|
-
SHA256.test(value.sha256 ?? "")
|
|
534
|
-
) ?? null;
|
|
359
|
+
prior?.entries.find((value) => value?.type === entry.type && value.id === entry.id &&
|
|
360
|
+
(entry.type !== "cron" || value.name === entry.name) &&
|
|
361
|
+
SHA256.test(value.sha256 ?? "")) ?? null;
|
|
535
362
|
|
|
536
363
|
// Preflight every skill and cron spec before the first mutation.
|
|
537
364
|
const skillPlans = new Map();
|
|
@@ -541,12 +368,7 @@ export async function reconcileDefaultProfileBundle({
|
|
|
541
368
|
cronSpecs.set(entry.id, cronDesired(entry));
|
|
542
369
|
continue;
|
|
543
370
|
}
|
|
544
|
-
const directory = join(
|
|
545
|
-
profileRoot,
|
|
546
|
-
"skills",
|
|
547
|
-
"sellable-defaults",
|
|
548
|
-
entry.id
|
|
549
|
-
);
|
|
371
|
+
const directory = join(profileRoot, "skills", "sellable-defaults", entry.id);
|
|
550
372
|
const path = join(directory, "SKILL.md");
|
|
551
373
|
if (existsSync(directory)) {
|
|
552
374
|
const stat = lstatSync(directory);
|
|
@@ -556,12 +378,8 @@ export async function reconcileDefaultProfileBundle({
|
|
|
556
378
|
if (name === "SKILL.md") continue;
|
|
557
379
|
const stage = join(directory, name);
|
|
558
380
|
const stageStat = lstatSync(stage);
|
|
559
|
-
if (
|
|
560
|
-
|
|
561
|
-
stageStat.isSymbolicLink() ||
|
|
562
|
-
!stageStat.isFile() ||
|
|
563
|
-
stageStat.nlink !== 1
|
|
564
|
-
) {
|
|
381
|
+
if (!name.startsWith(STAGE_PREFIX) || stageStat.isSymbolicLink() ||
|
|
382
|
+
!stageStat.isFile() || stageStat.nlink !== 1) {
|
|
565
383
|
throw new Error("default_profile_skill_collision");
|
|
566
384
|
}
|
|
567
385
|
rmSync(stage);
|
|
@@ -589,51 +407,31 @@ export async function reconcileDefaultProfileBundle({
|
|
|
589
407
|
throw new Error("default_profile_skill_readback_rejected");
|
|
590
408
|
}
|
|
591
409
|
actions.push({ type: "skill", id: entry.id, status: plan.status });
|
|
592
|
-
receiptEntries.push({
|
|
593
|
-
type: "skill",
|
|
594
|
-
id: entry.id,
|
|
595
|
-
sha256: entry.sha256,
|
|
596
|
-
});
|
|
410
|
+
receiptEntries.push({ type: "skill", id: entry.id, sha256: entry.sha256 });
|
|
597
411
|
continue;
|
|
598
412
|
}
|
|
599
413
|
|
|
600
|
-
if (
|
|
601
|
-
|
|
602
|
-
typeof cronAdapter?.update !== "function"
|
|
603
|
-
) {
|
|
414
|
+
if (typeof cronAdapter?.create !== "function" ||
|
|
415
|
+
typeof cronAdapter?.update !== "function") {
|
|
604
416
|
throw new Error("default_profile_cron_adapter_rejected");
|
|
605
417
|
}
|
|
606
|
-
const {
|
|
607
|
-
spec,
|
|
608
|
-
desired,
|
|
609
|
-
sha256: desiredSha256,
|
|
610
|
-
} = cronSpecs.get(entry.id);
|
|
418
|
+
const { spec, desired, sha256: desiredSha256 } = cronSpecs.get(entry.id);
|
|
611
419
|
const cron = await ensureHermesNamedCron({
|
|
612
420
|
profileRoot,
|
|
613
421
|
name: entry.name,
|
|
614
|
-
isExpected: (job) =>
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
sha256(
|
|
628
|
-
canonicalJson({
|
|
629
|
-
name: job?.name,
|
|
630
|
-
enabled: job?.enabled,
|
|
631
|
-
no_agent: job?.no_agent,
|
|
632
|
-
schedule: job?.schedule,
|
|
633
|
-
script: job?.script,
|
|
634
|
-
deliver: job?.deliver,
|
|
635
|
-
})
|
|
636
|
-
),
|
|
422
|
+
isExpected: (job) => Boolean(job?.id && job.name === desired.name &&
|
|
423
|
+
job.enabled === desired.enabled && job.no_agent === desired.no_agent &&
|
|
424
|
+
job.script === desired.script && job.deliver === desired.deliver &&
|
|
425
|
+
job.schedule?.kind === "cron" &&
|
|
426
|
+
job.schedule.expr === desired.schedule.expr),
|
|
427
|
+
isUpdatable: (job) => historical(entry)?.sha256 === sha256(canonicalJson({
|
|
428
|
+
name: job?.name,
|
|
429
|
+
enabled: job?.enabled,
|
|
430
|
+
no_agent: job?.no_agent,
|
|
431
|
+
schedule: job?.schedule,
|
|
432
|
+
script: job?.script,
|
|
433
|
+
deliver: job?.deliver,
|
|
434
|
+
})),
|
|
637
435
|
create: () =>
|
|
638
436
|
cronAdapter.create({
|
|
639
437
|
profileRoot,
|
|
@@ -654,17 +452,16 @@ export async function reconcileDefaultProfileBundle({
|
|
|
654
452
|
codePrefix: "default_profile_cron",
|
|
655
453
|
});
|
|
656
454
|
if (!cron.ok) throw new Error(cron.code);
|
|
657
|
-
const bound =
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
: { changed: false };
|
|
455
|
+
const bound = typeof cronAdapter.bind === "function"
|
|
456
|
+
? await cronAdapter.bind({
|
|
457
|
+
profileRoot,
|
|
458
|
+
entry,
|
|
459
|
+
spec,
|
|
460
|
+
desired,
|
|
461
|
+
job: cron.job,
|
|
462
|
+
activeProjection,
|
|
463
|
+
})
|
|
464
|
+
: { changed: false };
|
|
668
465
|
if (!bound || typeof bound.changed !== "boolean")
|
|
669
466
|
throw new Error("default_profile_cron_binding_rejected");
|
|
670
467
|
actions.push({
|
|
@@ -674,12 +471,7 @@ export async function reconcileDefaultProfileBundle({
|
|
|
674
471
|
status: cron.status,
|
|
675
472
|
bindingChanged: bound.changed,
|
|
676
473
|
});
|
|
677
|
-
receiptEntries.push({
|
|
678
|
-
type: "cron",
|
|
679
|
-
id: entry.id,
|
|
680
|
-
name: entry.name,
|
|
681
|
-
sha256: desiredSha256,
|
|
682
|
-
});
|
|
474
|
+
receiptEntries.push({ type: "cron", id: entry.id, name: entry.name, sha256: desiredSha256 });
|
|
683
475
|
}
|
|
684
476
|
|
|
685
477
|
const receipt = `${canonicalJson({
|
|
@@ -691,13 +483,10 @@ export async function reconcileDefaultProfileBundle({
|
|
|
691
483
|
entries: receiptEntries,
|
|
692
484
|
})}\n`;
|
|
693
485
|
const currentReceipt = existsSync(receiptPath)
|
|
694
|
-
? readRegular(receiptPath).toString("utf8")
|
|
695
|
-
|
|
696
|
-
if (currentReceipt !== receipt)
|
|
697
|
-
atomicWrite(receiptPath, receipt, 0o600);
|
|
486
|
+
? readRegular(receiptPath).toString("utf8") : null;
|
|
487
|
+
if (currentReceipt !== receipt) atomicWrite(receiptPath, receipt, 0o600);
|
|
698
488
|
const changed = actions.some(
|
|
699
|
-
(action) =>
|
|
700
|
-
action.status !== "REUSED" || action.bindingChanged === true
|
|
489
|
+
(action) => action.status !== "REUSED" || action.bindingChanged === true
|
|
701
490
|
);
|
|
702
491
|
return {
|
|
703
492
|
ok: true,
|
|
@@ -712,11 +501,8 @@ export async function reconcileDefaultProfileBundle({
|
|
|
712
501
|
{ dataRoot }
|
|
713
502
|
);
|
|
714
503
|
} catch (error) {
|
|
715
|
-
const code =
|
|
716
|
-
error
|
|
717
|
-
/^default_profile_[a-z0-9_]+$/.test(error.message)
|
|
718
|
-
? error.message
|
|
719
|
-
: "default_profile_reconcile_failed";
|
|
504
|
+
const code = error instanceof Error && /^default_profile_[a-z0-9_]+$/.test(error.message)
|
|
505
|
+
? error.message : "default_profile_reconcile_failed";
|
|
720
506
|
return refused(code);
|
|
721
507
|
}
|
|
722
508
|
}
|
|
@@ -41,7 +41,7 @@ ARG STRIPE_CLI_PACKAGE=@stripe/cli-linux-x64@1.45.0
|
|
|
41
41
|
ARG STRIPE_CLI_INTEGRITY=sha512-1ZhoPpoweYfynqsvhCLlTjZ7lPz5IKtxqAMT5MBy6zrwLRTP9x0T6r8h/jLYL8lRH1c7zC/6X6/XUu2V+r9Bog==
|
|
42
42
|
ARG DEFAULT_PROFILE_BUNDLE_FIXTURE_VERSION=1
|
|
43
43
|
ARG DEFAULT_PROFILE_BUNDLE_VERSION=1
|
|
44
|
-
ARG DEFAULT_PROFILE_BUNDLE_DIGEST=
|
|
44
|
+
ARG DEFAULT_PROFILE_BUNDLE_DIGEST=e2e306623a85ab6d3fa35409f9324b9cb583a9b8b12f8de1e33bdbdef98fa30f
|
|
45
45
|
|
|
46
46
|
COPY --from=slack-pp-cli-builder --chmod=0555 /out/slack-pp-cli /usr/local/bin/slack-pp-cli
|
|
47
47
|
|
|
@@ -52,7 +52,6 @@ import {
|
|
|
52
52
|
} from "../admin-native-gateway-supervisor.mjs";
|
|
53
53
|
import {
|
|
54
54
|
bindDefaultProfileProbeCron,
|
|
55
|
-
buildDefaultProfileMaterializerReceipt,
|
|
56
55
|
buildDefaultProfileProjection,
|
|
57
56
|
reconcileDefaultProfileBundle,
|
|
58
57
|
} from "../default-profile-reconciler.mjs";
|
|
@@ -264,11 +263,22 @@ function adminDefaultProfileCronAdapter(profileId, profileRoot) {
|
|
|
264
263
|
}
|
|
265
264
|
|
|
266
265
|
function adminMaterializerReceipt(profileId, profileRoot) {
|
|
267
|
-
|
|
266
|
+
const paths = [
|
|
267
|
+
"SOUL.md",
|
|
268
|
+
"skills/sellable/SKILL.md",
|
|
269
|
+
"skills/sellable-admin/SKILL.md",
|
|
270
|
+
];
|
|
271
|
+
return {
|
|
268
272
|
profileId,
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
273
|
+
fileHashes: Object.fromEntries(
|
|
274
|
+
paths
|
|
275
|
+
.filter((relative) => existsSync(join(profileRoot, relative)))
|
|
276
|
+
.map((relative) => [
|
|
277
|
+
relative,
|
|
278
|
+
sha256(readFileSync(join(profileRoot, relative))),
|
|
279
|
+
])
|
|
280
|
+
),
|
|
281
|
+
};
|
|
272
282
|
}
|
|
273
283
|
|
|
274
284
|
function canonicalJson(value) {
|
|
@@ -8,7 +8,7 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright
|
|
|
8
8
|
|
|
9
9
|
ARG MCP_PACKAGE=@sellable/mcp@0.1.880
|
|
10
10
|
ARG MCP_INTEGRITY=sha512-+Fo8aQlYduLaQMQff+ZA25LeUhWZ7I7UH5DECpND84LQ+rHSjKeKFj4Zs93bVHXwQCIy2OLbr75y4f4+CyT57w==
|
|
11
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.656
|
|
11
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.656
|
|
12
12
|
ARG ADMIN_MCP_PACKAGE=@sellable/admin-mcp@0.1.186-wip.phase158.20260802034727
|
|
13
13
|
ARG ADMIN_MCP_INTEGRITY=sha512-qYNew5wd2IfgVcp1UYOG9QvpF/8Mx19YJ5HQtMKzvSfUf3evePjtaKsoXLf2C/GUJ5QlLlHJo84VWOI3V01+9A==
|
|
14
14
|
ARG TUS_JS_CLIENT_VERSION=4.3.1
|
|
@@ -106,7 +106,7 @@ const BOOT_SESSION_ID = randomUUID();
|
|
|
106
106
|
const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.0";
|
|
107
107
|
const INSTALLER_PACKAGE =
|
|
108
108
|
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ??
|
|
109
|
-
"@sellable/install@0.1.656
|
|
109
|
+
"@sellable/install@0.1.656";
|
|
110
110
|
const MCP_PACKAGE =
|
|
111
111
|
process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.880";
|
|
112
112
|
const DEFAULT_PROFILE_BUNDLE_ROOT =
|
|
@@ -38,7 +38,7 @@ import { installAgentHostServices } from "./service-installer.mjs";
|
|
|
38
38
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
39
39
|
const VERSION = "sellable-agent-host-bootstrap/v1";
|
|
40
40
|
const RECEIPT_VERSION = "sellable-agent-host-registration/v1";
|
|
41
|
-
const INSTALLER_PACKAGE = "@sellable/install@0.1.656
|
|
41
|
+
const INSTALLER_PACKAGE = "@sellable/install@0.1.656";
|
|
42
42
|
const MCP_PACKAGE = "@sellable/mcp@0.1.880";
|
|
43
43
|
const HERMES_VERSION = "0.18.0";
|
|
44
44
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
@@ -1547,7 +1547,7 @@ export function compileClaimToProfileDesired(activeClaim, config) {
|
|
|
1547
1547
|
pinned.hermesCli !== "hermes" ||
|
|
1548
1548
|
pinned.hermesVersion !== "0.18.0" ||
|
|
1549
1549
|
pinned.installerPackage !==
|
|
1550
|
-
"@sellable/install@0.1.656
|
|
1550
|
+
"@sellable/install@0.1.656" ||
|
|
1551
1551
|
pinned.mcpPackage !== "@sellable/mcp@0.1.880" ||
|
|
1552
1552
|
!Array.isArray(policy.toolInclude) ||
|
|
1553
1553
|
policy.toolInclude.length === 0 ||
|
|
@@ -283,7 +283,7 @@ function validateDesired(desired) {
|
|
|
283
283
|
desired.hermesCli !== "hermes" ||
|
|
284
284
|
desired.hermesVersion !== "0.18.0" ||
|
|
285
285
|
desired.installerPackage !==
|
|
286
|
-
"@sellable/install@0.1.656
|
|
286
|
+
"@sellable/install@0.1.656" ||
|
|
287
287
|
desired.mcpPackage !== "@sellable/mcp@0.1.880" ||
|
|
288
288
|
!Array.isArray(desired.toolInclude) ||
|
|
289
289
|
desired.toolInclude.length === 0 ||
|
|
@@ -26,7 +26,7 @@ import { deriveContainedProfileId } from "./profile-materializer.mjs";
|
|
|
26
26
|
|
|
27
27
|
export const PROVISIONING_ACTION = "PROVISION_HERMES_PROFILE";
|
|
28
28
|
export const PINNED_INSTALL_PACKAGE =
|
|
29
|
-
"@sellable/install@0.1.656
|
|
29
|
+
"@sellable/install@0.1.656";
|
|
30
30
|
export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.880";
|
|
31
31
|
const PINNED_INSTALL_VERSION = PINNED_INSTALL_PACKAGE.slice(
|
|
32
32
|
PINNED_INSTALL_PACKAGE.lastIndexOf("@") + 1
|