@sellable/install 0.1.642 → 0.1.644
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 +7 -7
- package/container/README.md +1 -1
- package/lib/sellable-agent/default-profile-bundle.mjs +318 -0
- package/lib/sellable-agent/default-profile-bundles/customer/manifest.fragment.json +8 -0
- package/lib/sellable-agent/default-profile-bundles/fixtures/v1/manifest.fragment.json +26 -0
- package/lib/sellable-agent/default-profile-bundles/fixtures/v2/manifest.fragment.json +34 -0
- package/lib/sellable-agent/default-profile-bundles/shared/manifest.fragment.json +8 -0
- package/lib/sellable-agent/default-profile-reconciler.mjs +508 -0
- package/lib/sellable-agent/external-runtime-builder.mjs +1 -1
- package/lib/sellable-agent/fly-admin-image/Dockerfile +64 -4
- package/lib/sellable-agent/fly-admin-image/admin-runtime.mjs +165 -12
- package/lib/sellable-agent/fly-cron-proof-exec.mjs +218 -0
- package/lib/sellable-agent/fly-customer-image/Dockerfile +31 -6
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +132 -4
- package/lib/sellable-agent/fly-customer-worker.mjs +20 -2
- package/lib/sellable-agent/fly-runtime-identity.mjs +41 -2
- package/lib/sellable-agent/fly-skills-bridge.mjs +63 -7
- package/lib/sellable-agent/host-bootstrap.mjs +2 -2
- package/lib/sellable-agent/host-worker.mjs +2 -2
- package/lib/sellable-agent/profile-materializer.mjs +2 -2
- package/lib/sellable-agent/provisioning-adapter.mjs +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
chmodSync, closeSync, constants, existsSync, fstatSync, fsyncSync, lstatSync,
|
|
4
|
+
mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync,
|
|
5
|
+
rmSync, writeFileSync,
|
|
6
|
+
} from "node:fs";
|
|
7
|
+
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
8
|
+
|
|
9
|
+
import { withProfileSoulLock } from "./fly-soul-bridge.mjs";
|
|
10
|
+
import { ensureHermesNamedCron } from "./hermes-memory-snapshot.mjs";
|
|
11
|
+
|
|
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";
|
|
14
|
+
|
|
15
|
+
const BUNDLE_SCHEMA = "sellable-agent-default-profile-bundle/v1";
|
|
16
|
+
const ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
17
|
+
const MANAGED_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
18
|
+
const SHA256 = /^[a-f0-9]{64}$/;
|
|
19
|
+
const FENCE = /^[1-9][0-9]{0,19}$/;
|
|
20
|
+
const STAGE_PREFIX = ".sellable-default-stage-";
|
|
21
|
+
const MAX_BYTES = 1024 * 1024;
|
|
22
|
+
|
|
23
|
+
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
24
|
+
const canonicalJson = (value) => {
|
|
25
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
26
|
+
if (value && typeof value === "object") {
|
|
27
|
+
return `{${Object.keys(value).sort()
|
|
28
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
29
|
+
.join(",")}}`;
|
|
30
|
+
}
|
|
31
|
+
return JSON.stringify(value);
|
|
32
|
+
};
|
|
33
|
+
const refused = (code) => ({ ok: false, status: "REFUSED", code });
|
|
34
|
+
|
|
35
|
+
export function buildDefaultProfileProjection({
|
|
36
|
+
kind,
|
|
37
|
+
claim,
|
|
38
|
+
env = process.env,
|
|
39
|
+
imageBundle,
|
|
40
|
+
fixtureTarget,
|
|
41
|
+
} = {}) {
|
|
42
|
+
const operation = claim?.operation;
|
|
43
|
+
const authority = claim?.runtimeAuthority;
|
|
44
|
+
if (
|
|
45
|
+
authority?.bundleVersion === undefined &&
|
|
46
|
+
authority?.bundleDigest === undefined
|
|
47
|
+
) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const verifiedImage = imageBundle ?? {
|
|
51
|
+
bundleVersion: Number(
|
|
52
|
+
env.SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_VERSION ?? ""
|
|
53
|
+
),
|
|
54
|
+
bundleDigest:
|
|
55
|
+
env.SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_DIGEST ?? "",
|
|
56
|
+
};
|
|
57
|
+
if (
|
|
58
|
+
!["CUSTOMER", "ADMIN"].includes(kind) ||
|
|
59
|
+
!Number.isSafeInteger(verifiedImage.bundleVersion) ||
|
|
60
|
+
verifiedImage.bundleVersion < 1 ||
|
|
61
|
+
!SHA256.test(verifiedImage.bundleDigest)
|
|
62
|
+
) {
|
|
63
|
+
throw new Error("default_profile_image_bundle_rejected");
|
|
64
|
+
}
|
|
65
|
+
if (
|
|
66
|
+
!operation ||
|
|
67
|
+
!Number.isSafeInteger(authority?.authorityGeneration) ||
|
|
68
|
+
authority.authorityGeneration < 1 ||
|
|
69
|
+
authority.bundleVersion !== verifiedImage.bundleVersion ||
|
|
70
|
+
authority.bundleDigest !== verifiedImage.bundleDigest
|
|
71
|
+
) {
|
|
72
|
+
throw new Error("default_profile_bundle_identity_rejected");
|
|
73
|
+
}
|
|
74
|
+
const verifiedFixture = fixtureTarget ??
|
|
75
|
+
(env.SELLABLE_AGENT_DEFAULT_PROFILE_FIXTURE_TEST_ONLY === "1"
|
|
76
|
+
? {
|
|
77
|
+
testOnly: true,
|
|
78
|
+
workspaceId:
|
|
79
|
+
env.SELLABLE_AGENT_DEFAULT_PROFILE_FIXTURE_WORKSPACE_ID ?? "",
|
|
80
|
+
agentId:
|
|
81
|
+
env.SELLABLE_AGENT_DEFAULT_PROFILE_FIXTURE_AGENT_ID ?? "",
|
|
82
|
+
}
|
|
83
|
+
: undefined);
|
|
84
|
+
const exactFixture =
|
|
85
|
+
verifiedFixture?.testOnly === true &&
|
|
86
|
+
verifiedFixture.workspaceId === operation.workspaceId &&
|
|
87
|
+
verifiedFixture.agentId === operation.agentId
|
|
88
|
+
? { ...verifiedFixture }
|
|
89
|
+
: undefined;
|
|
90
|
+
return {
|
|
91
|
+
kind,
|
|
92
|
+
profileId: operation.profileId,
|
|
93
|
+
workspaceId: operation.workspaceId,
|
|
94
|
+
agentId: operation.agentId,
|
|
95
|
+
operationId: operation.id,
|
|
96
|
+
authorityGeneration: authority.authorityGeneration,
|
|
97
|
+
fence: String(operation.fence),
|
|
98
|
+
...verifiedImage,
|
|
99
|
+
...(exactFixture ? { fixtureTarget: exactFixture } : {}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function bindDefaultProfileProbeCron({
|
|
104
|
+
activeProjection,
|
|
105
|
+
profileRoot,
|
|
106
|
+
job,
|
|
107
|
+
bootId,
|
|
108
|
+
} = {}) {
|
|
109
|
+
if (!activeProjection?.fixtureTarget) return { changed: false };
|
|
110
|
+
if (
|
|
111
|
+
!isAbsolute(profileRoot ?? "") ||
|
|
112
|
+
!ID.test(bootId ?? "") ||
|
|
113
|
+
!ID.test(job?.id ?? "") ||
|
|
114
|
+
job.name !== "sellable-default-probe"
|
|
115
|
+
) {
|
|
116
|
+
throw new Error("default_profile_cron_binding_rejected");
|
|
117
|
+
}
|
|
118
|
+
const scriptPath = join(
|
|
119
|
+
profileRoot,
|
|
120
|
+
"scripts",
|
|
121
|
+
"sellable-default-probe.sh"
|
|
122
|
+
);
|
|
123
|
+
const binding = {
|
|
124
|
+
operationId: activeProjection.operationId,
|
|
125
|
+
bootId,
|
|
126
|
+
bundleDigest: activeProjection.bundleDigest,
|
|
127
|
+
nonce: sha256(
|
|
128
|
+
`${activeProjection.operationId}\0${bootId}\0${activeProjection.bundleDigest}`
|
|
129
|
+
).slice(0, 32),
|
|
130
|
+
jobId: job.id,
|
|
131
|
+
name: job.name,
|
|
132
|
+
};
|
|
133
|
+
const script = `#!/bin/sh
|
|
134
|
+
set -eu
|
|
135
|
+
exec /opt/hermes/.venv/bin/python3 - ${JSON.stringify(profileRoot)} <<'PY'
|
|
136
|
+
import json, os, sys, time
|
|
137
|
+
root = sys.argv[1]
|
|
138
|
+
binding = json.loads(${JSON.stringify(JSON.stringify(binding))})
|
|
139
|
+
binding["firedAt"] = int(time.time() * 1000)
|
|
140
|
+
marker_root = os.path.join(root, ".sellable-agent", "cron-markers")
|
|
141
|
+
os.makedirs(marker_root, mode=0o700, exist_ok=True)
|
|
142
|
+
target = os.path.join(marker_root, binding["name"] + ".json")
|
|
143
|
+
stage = target + ".tmp"
|
|
144
|
+
with open(stage, "w", encoding="utf-8") as handle:
|
|
145
|
+
json.dump(binding, handle, separators=(",", ":"))
|
|
146
|
+
handle.write("\\n")
|
|
147
|
+
os.chmod(stage, 0o600)
|
|
148
|
+
os.replace(stage, target)
|
|
149
|
+
PY
|
|
150
|
+
`;
|
|
151
|
+
const changed =
|
|
152
|
+
!existsSync(scriptPath) || readFileSync(scriptPath, "utf8") !== script;
|
|
153
|
+
if (changed) atomicWrite(scriptPath, script, 0o700);
|
|
154
|
+
return { changed, binding };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function readRegular(path, maxBytes = MAX_BYTES) {
|
|
158
|
+
const link = lstatSync(path);
|
|
159
|
+
if (link.isSymbolicLink() || !link.isFile() || link.nlink !== 1 || link.size > maxBytes)
|
|
160
|
+
throw new Error("default_profile_file_rejected");
|
|
161
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
162
|
+
try {
|
|
163
|
+
const opened = fstatSync(fd);
|
|
164
|
+
if (!opened.isFile() || opened.dev !== link.dev || opened.ino !== link.ino ||
|
|
165
|
+
opened.nlink !== 1 || opened.size !== link.size) {
|
|
166
|
+
throw new Error("default_profile_file_rejected");
|
|
167
|
+
}
|
|
168
|
+
return readFileSync(fd);
|
|
169
|
+
} finally {
|
|
170
|
+
closeSync(fd);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function atomicWrite(path, bytes, mode) {
|
|
175
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
176
|
+
const stage = join(dirname(path), `${STAGE_PREFIX}${basename(path)}-${process.pid}-${randomUUID()}`);
|
|
177
|
+
writeFileSync(stage, bytes, { flag: "wx", mode });
|
|
178
|
+
chmodSync(stage, mode);
|
|
179
|
+
const fd = openSync(stage, constants.O_RDONLY);
|
|
180
|
+
try {
|
|
181
|
+
fsyncSync(fd);
|
|
182
|
+
} finally {
|
|
183
|
+
closeSync(fd);
|
|
184
|
+
}
|
|
185
|
+
renameSync(stage, path);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function readBundle(bundleRoot) {
|
|
189
|
+
if (!isAbsolute(bundleRoot ?? "") || !existsSync(bundleRoot) ||
|
|
190
|
+
lstatSync(bundleRoot).isSymbolicLink() || !lstatSync(bundleRoot).isDirectory() ||
|
|
191
|
+
realpathSync(bundleRoot) !== bundleRoot) {
|
|
192
|
+
throw new Error("default_profile_bundle_rejected");
|
|
193
|
+
}
|
|
194
|
+
const manifest = JSON.parse(readRegular(join(bundleRoot, "manifest.json")).toString("utf8"));
|
|
195
|
+
if (
|
|
196
|
+
manifest?.schemaVersion !== BUNDLE_SCHEMA ||
|
|
197
|
+
!["CUSTOMER", "ADMIN"].includes(manifest.kind) ||
|
|
198
|
+
!Number.isSafeInteger(manifest.bundleVersion) || manifest.bundleVersion < 1 ||
|
|
199
|
+
!SHA256.test(manifest.bundleDigest ?? "") ||
|
|
200
|
+
!Array.isArray(manifest.materializerReceipts) ||
|
|
201
|
+
!Array.isArray(manifest.files)
|
|
202
|
+
) {
|
|
203
|
+
throw new Error("default_profile_bundle_rejected");
|
|
204
|
+
}
|
|
205
|
+
const files = manifest.files.map((entry) => {
|
|
206
|
+
const expectedPath = entry?.type === "skill"
|
|
207
|
+
? `skills/sellable-defaults/${entry.id}/SKILL.md`
|
|
208
|
+
: `crons/${entry?.name}.json`;
|
|
209
|
+
if (
|
|
210
|
+
!MANAGED_ID.test(entry?.id ?? "") ||
|
|
211
|
+
!["skill", "cron"].includes(entry.type) ||
|
|
212
|
+
entry.ownership !== "system_managed" ||
|
|
213
|
+
!["SHARED", "CUSTOMER", "ADMIN", "FIXTURE"].includes(entry.kind) ||
|
|
214
|
+
entry.mode !== "0444" ||
|
|
215
|
+
entry.path !== expectedPath ||
|
|
216
|
+
!SHA256.test(entry.sha256 ?? "") ||
|
|
217
|
+
(entry.type === "cron" && (!MANAGED_ID.test(entry.name ?? "") ||
|
|
218
|
+
!entry.name.startsWith("sellable-default-")))
|
|
219
|
+
) {
|
|
220
|
+
throw new Error("default_profile_bundle_rejected");
|
|
221
|
+
}
|
|
222
|
+
const content = readRegular(join(bundleRoot, ...entry.path.split("/")), 64 * 1024);
|
|
223
|
+
if (sha256(content) !== entry.sha256)
|
|
224
|
+
throw new Error("default_profile_bundle_rejected");
|
|
225
|
+
return { ...entry, content: content.toString("utf8") };
|
|
226
|
+
});
|
|
227
|
+
const { bundleDigest, ...unsignedManifest } = manifest;
|
|
228
|
+
if (sha256(canonicalJson({ manifest: unsignedManifest, files })) !== bundleDigest)
|
|
229
|
+
throw new Error("default_profile_bundle_rejected");
|
|
230
|
+
return { manifest, files };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function activeEntries(bundle, projection) {
|
|
234
|
+
if (bundle.manifest.kind !== projection.kind ||
|
|
235
|
+
bundle.manifest.bundleVersion !== projection.bundleVersion ||
|
|
236
|
+
bundle.manifest.bundleDigest !== projection.bundleDigest) {
|
|
237
|
+
throw new Error("default_profile_projection_mismatch");
|
|
238
|
+
}
|
|
239
|
+
const target = projection.fixtureTarget;
|
|
240
|
+
const fixtureAllowed = target?.testOnly === true &&
|
|
241
|
+
target.workspaceId === projection.workspaceId &&
|
|
242
|
+
target.agentId === projection.agentId;
|
|
243
|
+
return bundle.files
|
|
244
|
+
.filter((entry) => entry.kind !== "FIXTURE" || fixtureAllowed)
|
|
245
|
+
.sort(
|
|
246
|
+
(left, right) => (left.type === "skill" ? 0 : 1) -
|
|
247
|
+
(right.type === "skill" ? 0 : 1) ||
|
|
248
|
+
left.path.localeCompare(right.path)
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function validateProjection(value) {
|
|
253
|
+
return Boolean(
|
|
254
|
+
value &&
|
|
255
|
+
["CUSTOMER", "ADMIN"].includes(value.kind) &&
|
|
256
|
+
["profileId", "workspaceId", "agentId", "operationId"].every(
|
|
257
|
+
(key) => ID.test(value[key] ?? "")
|
|
258
|
+
) &&
|
|
259
|
+
Number.isSafeInteger(value.authorityGeneration) &&
|
|
260
|
+
value.authorityGeneration > 0 &&
|
|
261
|
+
FENCE.test(value.fence ?? "") &&
|
|
262
|
+
Number.isSafeInteger(value.bundleVersion) &&
|
|
263
|
+
value.bundleVersion > 0 &&
|
|
264
|
+
SHA256.test(value.bundleDigest ?? "")
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function cronDesired(entry) {
|
|
269
|
+
const spec = JSON.parse(entry.content);
|
|
270
|
+
if (
|
|
271
|
+
!spec ||
|
|
272
|
+
Object.keys(spec).sort().join("\0") !==
|
|
273
|
+
"deliver\0marker\0noAgent\0schedule\0script" ||
|
|
274
|
+
typeof spec.schedule !== "string" ||
|
|
275
|
+
!/^(?:\S+\s+){4}\S+$/.test(spec.schedule) ||
|
|
276
|
+
!/^[a-z0-9][a-z0-9-]{0,63}\.sh$/.test(spec.script ?? "") ||
|
|
277
|
+
spec.noAgent !== true ||
|
|
278
|
+
spec.deliver !== "local" ||
|
|
279
|
+
!MANAGED_ID.test(spec.marker ?? "")
|
|
280
|
+
) {
|
|
281
|
+
throw new Error("default_profile_cron_spec_rejected");
|
|
282
|
+
}
|
|
283
|
+
const desired = {
|
|
284
|
+
name: entry.name,
|
|
285
|
+
enabled: true,
|
|
286
|
+
no_agent: true,
|
|
287
|
+
schedule: { kind: "cron", expr: spec.schedule },
|
|
288
|
+
script: spec.script,
|
|
289
|
+
deliver: spec.deliver,
|
|
290
|
+
};
|
|
291
|
+
return { spec, desired, sha256: sha256(canonicalJson(desired)) };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export async function reconcileDefaultProfileBundle({
|
|
295
|
+
activeProjection,
|
|
296
|
+
materializerReceipt,
|
|
297
|
+
profileRoot,
|
|
298
|
+
bundleRoot,
|
|
299
|
+
dataRoot,
|
|
300
|
+
withProfileLock = withProfileSoulLock,
|
|
301
|
+
reattestAuthority,
|
|
302
|
+
cronAdapter,
|
|
303
|
+
} = {}) {
|
|
304
|
+
if (activeProjection == null)
|
|
305
|
+
return { ok: true, status: "EMPTY", changed: false, actions: [] };
|
|
306
|
+
if (!validateProjection(activeProjection))
|
|
307
|
+
return refused("default_profile_projection_rejected");
|
|
308
|
+
if (!isAbsolute(dataRoot ?? "") || !isAbsolute(profileRoot ?? "") ||
|
|
309
|
+
typeof withProfileLock !== "function" || typeof reattestAuthority !== "function") {
|
|
310
|
+
return refused("default_profile_reconcile_input_rejected");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
if (activeEntries(readBundle(bundleRoot), activeProjection).length === 0)
|
|
315
|
+
return { ok: true, status: "EMPTY", changed: false, actions: [] };
|
|
316
|
+
return await withProfileLock(
|
|
317
|
+
activeProjection.profileId,
|
|
318
|
+
async () => {
|
|
319
|
+
const authority = await reattestAuthority(activeProjection);
|
|
320
|
+
if (authority?.operationId !== activeProjection.operationId ||
|
|
321
|
+
authority?.authorityGeneration !== activeProjection.authorityGeneration ||
|
|
322
|
+
authority?.fence !== activeProjection.fence) {
|
|
323
|
+
return refused("default_profile_authority_stale");
|
|
324
|
+
}
|
|
325
|
+
if (!existsSync(profileRoot) || lstatSync(profileRoot).isSymbolicLink() ||
|
|
326
|
+
!lstatSync(profileRoot).isDirectory() || realpathSync(profileRoot) !== profileRoot) {
|
|
327
|
+
throw new Error("default_profile_root_rejected");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const bundle = readBundle(bundleRoot);
|
|
331
|
+
const entries = activeEntries(bundle, activeProjection);
|
|
332
|
+
if (materializerReceipt?.profileId !== activeProjection.profileId ||
|
|
333
|
+
!materializerReceipt.fileHashes || Array.isArray(materializerReceipt.fileHashes)) {
|
|
334
|
+
throw new Error("default_profile_materializer_receipt_rejected");
|
|
335
|
+
}
|
|
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));
|
|
348
|
+
|
|
349
|
+
const receiptPath = join(profileRoot, DEFAULT_PROFILE_RECONCILE_RECEIPT);
|
|
350
|
+
let prior = null;
|
|
351
|
+
if (existsSync(receiptPath)) {
|
|
352
|
+
prior = JSON.parse(readRegular(receiptPath).toString("utf8"));
|
|
353
|
+
if (prior?.schemaVersion !== DEFAULT_PROFILE_RECONCILE_SCHEMA ||
|
|
354
|
+
prior.profileId !== activeProjection.profileId || !Array.isArray(prior.entries)) {
|
|
355
|
+
throw new Error("default_profile_receipt_rejected");
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const historical = (entry) =>
|
|
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;
|
|
362
|
+
|
|
363
|
+
// Preflight every skill and cron spec before the first mutation.
|
|
364
|
+
const skillPlans = new Map();
|
|
365
|
+
const cronSpecs = new Map();
|
|
366
|
+
for (const entry of entries) {
|
|
367
|
+
if (entry.type === "cron") {
|
|
368
|
+
cronSpecs.set(entry.id, cronDesired(entry));
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const directory = join(profileRoot, "skills", "sellable-defaults", entry.id);
|
|
372
|
+
const path = join(directory, "SKILL.md");
|
|
373
|
+
if (existsSync(directory)) {
|
|
374
|
+
const stat = lstatSync(directory);
|
|
375
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
376
|
+
throw new Error("default_profile_skill_collision");
|
|
377
|
+
for (const name of readdirSync(directory)) {
|
|
378
|
+
if (name === "SKILL.md") continue;
|
|
379
|
+
const stage = join(directory, name);
|
|
380
|
+
const stageStat = lstatSync(stage);
|
|
381
|
+
if (!name.startsWith(STAGE_PREFIX) || stageStat.isSymbolicLink() ||
|
|
382
|
+
!stageStat.isFile() || stageStat.nlink !== 1) {
|
|
383
|
+
throw new Error("default_profile_skill_collision");
|
|
384
|
+
}
|
|
385
|
+
rmSync(stage);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
let status = "CREATED";
|
|
389
|
+
if (existsSync(path)) {
|
|
390
|
+
const current = sha256(readRegular(path, 64 * 1024));
|
|
391
|
+
if (current === entry.sha256) status = "REUSED";
|
|
392
|
+
else if (historical(entry)?.sha256 === current) status = "UPDATED";
|
|
393
|
+
else throw new Error("default_profile_skill_collision");
|
|
394
|
+
}
|
|
395
|
+
skillPlans.set(entry.id, { directory, path, status });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const actions = [];
|
|
399
|
+
const receiptEntries = [];
|
|
400
|
+
for (const entry of entries) {
|
|
401
|
+
if (entry.type === "skill") {
|
|
402
|
+
const plan = skillPlans.get(entry.id);
|
|
403
|
+
if (plan.status !== "REUSED") {
|
|
404
|
+
mkdirSync(plan.directory, { recursive: true, mode: 0o700 });
|
|
405
|
+
atomicWrite(plan.path, entry.content, 0o444);
|
|
406
|
+
if (sha256(readRegular(plan.path, 64 * 1024)) !== entry.sha256)
|
|
407
|
+
throw new Error("default_profile_skill_readback_rejected");
|
|
408
|
+
}
|
|
409
|
+
actions.push({ type: "skill", id: entry.id, status: plan.status });
|
|
410
|
+
receiptEntries.push({ type: "skill", id: entry.id, sha256: entry.sha256 });
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (typeof cronAdapter?.create !== "function" ||
|
|
415
|
+
typeof cronAdapter?.update !== "function") {
|
|
416
|
+
throw new Error("default_profile_cron_adapter_rejected");
|
|
417
|
+
}
|
|
418
|
+
const { spec, desired, sha256: desiredSha256 } = cronSpecs.get(entry.id);
|
|
419
|
+
const cron = await ensureHermesNamedCron({
|
|
420
|
+
profileRoot,
|
|
421
|
+
name: entry.name,
|
|
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
|
+
})),
|
|
435
|
+
create: () =>
|
|
436
|
+
cronAdapter.create({
|
|
437
|
+
profileRoot,
|
|
438
|
+
entry,
|
|
439
|
+
spec,
|
|
440
|
+
desired,
|
|
441
|
+
activeProjection,
|
|
442
|
+
}),
|
|
443
|
+
update: (existing) =>
|
|
444
|
+
cronAdapter.update({
|
|
445
|
+
profileRoot,
|
|
446
|
+
entry,
|
|
447
|
+
spec,
|
|
448
|
+
desired,
|
|
449
|
+
existing,
|
|
450
|
+
activeProjection,
|
|
451
|
+
}),
|
|
452
|
+
codePrefix: "default_profile_cron",
|
|
453
|
+
});
|
|
454
|
+
if (!cron.ok) throw new Error(cron.code);
|
|
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 };
|
|
465
|
+
if (!bound || typeof bound.changed !== "boolean")
|
|
466
|
+
throw new Error("default_profile_cron_binding_rejected");
|
|
467
|
+
actions.push({
|
|
468
|
+
type: "cron",
|
|
469
|
+
id: entry.id,
|
|
470
|
+
name: entry.name,
|
|
471
|
+
status: cron.status,
|
|
472
|
+
bindingChanged: bound.changed,
|
|
473
|
+
});
|
|
474
|
+
receiptEntries.push({ type: "cron", id: entry.id, name: entry.name, sha256: desiredSha256 });
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const receipt = `${canonicalJson({
|
|
478
|
+
schemaVersion: DEFAULT_PROFILE_RECONCILE_SCHEMA,
|
|
479
|
+
profileId: activeProjection.profileId,
|
|
480
|
+
bundleVersion: activeProjection.bundleVersion,
|
|
481
|
+
bundleDigest: activeProjection.bundleDigest,
|
|
482
|
+
materializerReceiptSha256,
|
|
483
|
+
entries: receiptEntries,
|
|
484
|
+
})}\n`;
|
|
485
|
+
const currentReceipt = existsSync(receiptPath)
|
|
486
|
+
? readRegular(receiptPath).toString("utf8") : null;
|
|
487
|
+
if (currentReceipt !== receipt) atomicWrite(receiptPath, receipt, 0o600);
|
|
488
|
+
const changed = actions.some(
|
|
489
|
+
(action) => action.status !== "REUSED" || action.bindingChanged === true
|
|
490
|
+
);
|
|
491
|
+
return {
|
|
492
|
+
ok: true,
|
|
493
|
+
status: changed ? "APPLIED" : "REUSED",
|
|
494
|
+
changed,
|
|
495
|
+
bundleVersion: activeProjection.bundleVersion,
|
|
496
|
+
bundleDigest: activeProjection.bundleDigest,
|
|
497
|
+
materializerReceiptSha256,
|
|
498
|
+
actions,
|
|
499
|
+
};
|
|
500
|
+
},
|
|
501
|
+
{ dataRoot }
|
|
502
|
+
);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
const code = error instanceof Error && /^default_profile_[a-z0-9_]+$/.test(error.message)
|
|
505
|
+
? error.message : "default_profile_reconcile_failed";
|
|
506
|
+
return refused(code);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
|
|
29
29
|
const MANIFEST_VERSION = "sellable-agent-external-runtime-closure/v1";
|
|
30
30
|
const MCP_PACKAGE = "@sellable/mcp";
|
|
31
|
-
const MCP_VERSION = "0.1.
|
|
31
|
+
const MCP_VERSION = "0.1.880";
|
|
32
32
|
const SAFE_COMPONENT = /^[A-Za-z0-9@._+-]+$/;
|
|
33
33
|
const HERMES_EXCLUDES = new Set([".git", ".playwright", "node_modules"]);
|
|
34
34
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1.7
|
|
1
2
|
ARG HERMES_IMAGE=nousresearch/hermes-agent@sha256:c0cab4e3711bcb27a312be1b3776254fc06fd50d5f7a6b8017915fc7171cb39e
|
|
2
3
|
ARG SLACK_PP_GO_IMAGE=golang@sha256:386d475a660466863d9f8c766fec64d7fdad3edac2c6a05020c09534d71edb4b
|
|
3
4
|
|
|
@@ -26,6 +27,8 @@ ARG ADMIN_MCP_PACKAGE
|
|
|
26
27
|
ARG ADMIN_MCP_INTEGRITY
|
|
27
28
|
ARG ADMIN_INSTALL_PACKAGE
|
|
28
29
|
ARG ADMIN_INSTALL_INTEGRITY
|
|
30
|
+
ARG ADMIN_INSTALL_SOURCE=registry
|
|
31
|
+
ARG ADMIN_INSTALL_LOCAL_SHA256
|
|
29
32
|
ARG ADMIN_LOCKED_TREE_DIGEST
|
|
30
33
|
ARG ADMIN_ARTIFACT_DIGEST
|
|
31
34
|
ARG ADMIN_PACKAGE_LOCK_B64
|
|
@@ -36,16 +39,28 @@ ARG AGENT_BROWSER_VERSION=0.26.0
|
|
|
36
39
|
ARG PLAYWRIGHT_BROWSER_REVISION=1234
|
|
37
40
|
ARG STRIPE_CLI_PACKAGE=@stripe/cli-linux-x64@1.45.0
|
|
38
41
|
ARG STRIPE_CLI_INTEGRITY=sha512-1ZhoPpoweYfynqsvhCLlTjZ7lPz5IKtxqAMT5MBy6zrwLRTP9x0T6r8h/jLYL8lRH1c7zC/6X6/XUu2V+r9Bog==
|
|
42
|
+
ARG DEFAULT_PROFILE_BUNDLE_FIXTURE_VERSION=
|
|
43
|
+
ARG DEFAULT_PROFILE_BUNDLE_VERSION=1
|
|
44
|
+
ARG DEFAULT_PROFILE_BUNDLE_DIGEST=608ab392a8d8477ea0045ac89860af7e8be98740c23b21225fb82e07aad10723
|
|
39
45
|
|
|
40
46
|
COPY --from=slack-pp-cli-builder --chmod=0555 /out/slack-pp-cli /usr/local/bin/slack-pp-cli
|
|
41
47
|
|
|
42
|
-
RUN
|
|
48
|
+
RUN --mount=type=secret,id=admin_install_tarball,required=false \
|
|
49
|
+
set -eux; \
|
|
50
|
+
test "${ADMIN_INSTALL_SOURCE}" = "registry" -o "${ADMIN_INSTALL_SOURCE}" = "local-tarball"; \
|
|
43
51
|
test -n "${ADMIN_PACKAGE_LOCK_B64}"; \
|
|
44
52
|
test -n "${ADMIN_PROFILE_CONTRACT_TEMPLATE_B64}"; \
|
|
45
53
|
test "$(npm view "${PRODUCT_MCP_PACKAGE}" dist.integrity)" = "${PRODUCT_MCP_INTEGRITY}"; \
|
|
46
54
|
test "$(npm view "${PRODUCT_INSTALL_PACKAGE}" dist.integrity)" = "${PRODUCT_INSTALL_INTEGRITY}"; \
|
|
47
55
|
test "$(npm view "${ADMIN_MCP_PACKAGE}" dist.integrity)" = "${ADMIN_MCP_INTEGRITY}"; \
|
|
48
|
-
test "$
|
|
56
|
+
if test "${ADMIN_INSTALL_SOURCE}" = "registry"; then \
|
|
57
|
+
test "$(npm view "${ADMIN_INSTALL_PACKAGE}" dist.integrity)" = "${ADMIN_INSTALL_INTEGRITY}"; \
|
|
58
|
+
else \
|
|
59
|
+
test -s /run/secrets/admin_install_tarball; \
|
|
60
|
+
test -n "${ADMIN_INSTALL_LOCAL_SHA256}"; \
|
|
61
|
+
test "$(sha256sum /run/secrets/admin_install_tarball | cut -d ' ' -f 1)" = "${ADMIN_INSTALL_LOCAL_SHA256}"; \
|
|
62
|
+
test "$(node -e 'const c=require("crypto"),f=require("fs"); process.stdout.write(`sha512-${c.createHash("sha512").update(f.readFileSync(process.argv[1])).digest("base64")}`)' /run/secrets/admin_install_tarball)" = "${ADMIN_INSTALL_INTEGRITY}"; \
|
|
63
|
+
fi; \
|
|
49
64
|
test "$(npm view "${STRIPE_CLI_PACKAGE}" dist.integrity)" = "${STRIPE_CLI_INTEGRITY}"; \
|
|
50
65
|
mkdir -p /usr/local/lib/sellable-agent/admin-artifact; \
|
|
51
66
|
node -e 'const fs=require("fs"); const specs=process.argv.slice(1); fs.writeFileSync("/usr/local/lib/sellable-agent/admin-artifact/package.json", JSON.stringify({name:"sellable-admin-runtime-artifact",private:true,version:"1.0.0",dependencies:Object.fromEntries(specs.map((spec)=>{const at=spec.lastIndexOf("@"); return [spec.slice(0,at),spec.slice(at+1)];}))},null,2)+"\n");' \
|
|
@@ -58,12 +73,28 @@ RUN set -eux; \
|
|
|
58
73
|
printf '%s' "${ADMIN_PROFILE_CONTRACT_TEMPLATE_B64}" | base64 -d \
|
|
59
74
|
> /usr/local/lib/sellable-agent/admin-artifact/profile-contract-template.json; \
|
|
60
75
|
test "$(sha256sum /usr/local/lib/sellable-agent/admin-artifact/package-lock.json | cut -d ' ' -f 1)" = "${ADMIN_LOCKED_TREE_DIGEST}"; \
|
|
76
|
+
if test "${ADMIN_INSTALL_SOURCE}" = "local-tarball"; then \
|
|
77
|
+
node -e 'const fs=require("fs"); const packagePath=process.argv[1],lockPath=process.argv[2]; const p=JSON.parse(fs.readFileSync(packagePath)); delete p.dependencies["@sellable/admin-install"]; fs.writeFileSync(packagePath,JSON.stringify(p,null,2)+"\n"); const l=JSON.parse(fs.readFileSync(lockPath)); delete l.packages[""].dependencies["@sellable/admin-install"]; delete l.packages["node_modules/@sellable/admin-install"]; fs.writeFileSync(lockPath,JSON.stringify(l,null,2)+"\n");' \
|
|
78
|
+
/usr/local/lib/sellable-agent/admin-artifact/package.json \
|
|
79
|
+
/usr/local/lib/sellable-agent/admin-artifact/package-lock.json; \
|
|
80
|
+
fi; \
|
|
61
81
|
npm ci \
|
|
62
82
|
--prefix /usr/local/lib/sellable-agent/admin-artifact \
|
|
63
83
|
--ignore-scripts \
|
|
64
84
|
--omit=dev \
|
|
65
85
|
--no-audit \
|
|
66
86
|
--no-fund; \
|
|
87
|
+
if test "${ADMIN_INSTALL_SOURCE}" = "local-tarball"; then \
|
|
88
|
+
npm install \
|
|
89
|
+
--prefix /usr/local/lib/sellable-agent/admin-artifact \
|
|
90
|
+
--ignore-scripts \
|
|
91
|
+
--omit=dev \
|
|
92
|
+
--no-audit \
|
|
93
|
+
--no-fund \
|
|
94
|
+
--no-save \
|
|
95
|
+
--package-lock=false \
|
|
96
|
+
/run/secrets/admin_install_tarball; \
|
|
97
|
+
fi; \
|
|
67
98
|
npm install \
|
|
68
99
|
--prefix /usr/local/lib/sellable-agent \
|
|
69
100
|
--omit=dev \
|
|
@@ -94,6 +125,8 @@ RUN set -eux; \
|
|
|
94
125
|
test -f /usr/local/lib/sellable-agent/admin-artifact/node_modules/@sellable/admin-mcp/dist/index.js; \
|
|
95
126
|
node -e 'const p=require("/usr/local/lib/sellable-agent/admin-artifact/node_modules/@sellable/admin-mcp/package.json"); if(p.main!=="dist/index.js" || p.bin?.["sellable-admin-mcp"]!=="dist/index.js") process.exit(1);'; \
|
|
96
127
|
test -f /usr/local/lib/sellable-agent/admin-artifact/node_modules/@sellable/admin-install/lib/hermes-admin-profile-factory.mjs; \
|
|
128
|
+
test -f /usr/local/lib/sellable-agent/admin-artifact/node_modules/@sellable/admin-install/default-profile-bundles/admin/manifest.fragment.json; \
|
|
129
|
+
test "$(node -p "require('/usr/local/lib/sellable-agent/admin-artifact/node_modules/@sellable/admin-install/package.json').version")" = "${ADMIN_INSTALL_PACKAGE##*@}"; \
|
|
97
130
|
node -e 'const fs=require("fs"),c=require("crypto"); const p="/usr/local/lib/sellable-agent/admin-artifact/profile-contract-template.json"; const stable=(v)=>Array.isArray(v)?`[${v.map(stable).join(",")}]`:v&&typeof v==="object"?`{${Object.keys(v).sort().map((k)=>`${JSON.stringify(k)}:${stable(v[k])}`).join(",")}}`:JSON.stringify(v); const t=JSON.parse(fs.readFileSync(p)); const {contentDigest,...body}=t; if(t.schemaVersion!=="sellable-admin-profile-contract-template/v1" || c.createHash("sha256").update(stable(body)).digest("hex")!==contentDigest) process.exit(1);'; \
|
|
98
131
|
rm -f /etc/s6-overlay/s6-rc.d/user/contents.d/dashboard \
|
|
99
132
|
/etc/s6-overlay/s6-rc.d/user/contents.d/main-hermes \
|
|
@@ -132,12 +165,29 @@ COPY fly-skills-bridge-exec.mjs /usr/local/lib/sellable-agent/fly-skills-bridge-
|
|
|
132
165
|
COPY fly-capabilities-bridge.mjs /usr/local/lib/sellable-agent/fly-capabilities-bridge.mjs
|
|
133
166
|
COPY fly-capabilities-bridge-exec.mjs /usr/local/lib/sellable-agent/fly-capabilities-bridge-exec.mjs
|
|
134
167
|
COPY soul-artifact-validator.mjs /usr/local/lib/sellable-agent/soul-artifact-validator.mjs
|
|
168
|
+
COPY default-profile-bundle.mjs /usr/local/lib/sellable-agent/default-profile-bundle.mjs
|
|
169
|
+
COPY default-profile-reconciler.mjs /usr/local/lib/sellable-agent/default-profile-reconciler.mjs
|
|
170
|
+
COPY fly-cron-proof-exec.mjs /usr/local/lib/sellable-agent/fly-cron-proof-exec.mjs
|
|
171
|
+
COPY default-profile-bundles/shared/ /usr/local/lib/sellable-agent/default-profile-bundles/shared/
|
|
172
|
+
COPY default-profile-bundles/fixtures/ /usr/local/lib/sellable-agent/default-profile-bundles/fixtures/
|
|
135
173
|
COPY hermes-memory-dirty.mjs /opt/sellable/bin/hermes-memory-dirty
|
|
136
174
|
COPY hermes-memory-reconcile.sh /opt/sellable/share/hermes-memory-reconcile.sh
|
|
137
175
|
COPY hermes-endpoint-cron.mjs /opt/sellable/bin/hermes-endpoint-cron
|
|
138
176
|
COPY fly-admin-image/rootfs/ /
|
|
139
177
|
|
|
140
|
-
RUN
|
|
178
|
+
RUN set -eux; \
|
|
179
|
+
fixture_args=""; \
|
|
180
|
+
if test -n "${DEFAULT_PROFILE_BUNDLE_FIXTURE_VERSION}"; then \
|
|
181
|
+
fixture_args="--fixture-version ${DEFAULT_PROFILE_BUNDLE_FIXTURE_VERSION}"; \
|
|
182
|
+
fi; \
|
|
183
|
+
node /usr/local/lib/sellable-agent/default-profile-bundle.mjs \
|
|
184
|
+
--kind ADMIN ${fixture_args} \
|
|
185
|
+
--admin-fragment /usr/local/lib/sellable-agent/admin-artifact/node_modules/@sellable/admin-install/default-profile-bundles/admin/manifest.fragment.json \
|
|
186
|
+
--output /usr/local/share/sellable-agent/default-profile-bundle \
|
|
187
|
+
&& node -e \
|
|
188
|
+
'const fs=require("fs"); const manifest=JSON.parse(fs.readFileSync("/usr/local/share/sellable-agent/default-profile-bundle/manifest.json")); if(String(manifest.bundleVersion)!==process.argv[1] || manifest.bundleDigest!==process.argv[2]) process.exit(1);' \
|
|
189
|
+
"${DEFAULT_PROFILE_BUNDLE_VERSION}" "${DEFAULT_PROFILE_BUNDLE_DIGEST}" \
|
|
190
|
+
&& node --input-type=module -e \
|
|
141
191
|
'import { installHermesAgentBridge, HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT } from "/usr/local/lib/sellable-agent/hermes-bridge.mjs"; installHermesAgentBridge({ sourceRoot: "/opt/hermes", contract: HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT });' \
|
|
142
192
|
&& node --input-type=module -e \
|
|
143
193
|
'await import("/usr/local/lib/sellable-agent/fly-runtime-identity.mjs");' \
|
|
@@ -169,11 +219,15 @@ RUN node --input-type=module -e \
|
|
|
169
219
|
&& sha256sum /usr/local/lib/sellable-agent/fly-skills-bridge.mjs \
|
|
170
220
|
/usr/local/lib/sellable-agent/fly-skills-bridge-exec.mjs \
|
|
171
221
|
> /usr/local/share/sellable-agent/skills-bridge-source.sha256 \
|
|
222
|
+
&& sha256sum /usr/local/lib/sellable-agent/default-profile-reconciler.mjs \
|
|
223
|
+
/usr/local/lib/sellable-agent/fly-cron-proof-exec.mjs \
|
|
224
|
+
> /usr/local/share/sellable-agent/default-profile-reconcile-source.sha256 \
|
|
172
225
|
&& sha256sum /usr/local/lib/sellable-agent/fly-capabilities-bridge.mjs \
|
|
173
226
|
/usr/local/lib/sellable-agent/fly-capabilities-bridge-exec.mjs \
|
|
174
227
|
> /usr/local/share/sellable-agent/capabilities-bridge-source.sha256 \
|
|
175
228
|
&& chmod 0444 /usr/local/share/sellable-agent/soul-bridge-source.sha256 \
|
|
176
229
|
/usr/local/share/sellable-agent/skills-bridge-source.sha256 \
|
|
230
|
+
/usr/local/share/sellable-agent/default-profile-reconcile-source.sha256 \
|
|
177
231
|
/usr/local/share/sellable-agent/capabilities-bridge-source.sha256 \
|
|
178
232
|
&& chmod 0755 /etc \
|
|
179
233
|
&& chmod 0555 \
|
|
@@ -192,6 +246,8 @@ RUN node --input-type=module -e \
|
|
|
192
246
|
/usr/local/lib/sellable-agent/fly-soul-bridge-exec.mjs \
|
|
193
247
|
/usr/local/lib/sellable-agent/fly-skills-bridge.mjs \
|
|
194
248
|
/usr/local/lib/sellable-agent/fly-skills-bridge-exec.mjs \
|
|
249
|
+
/usr/local/lib/sellable-agent/default-profile-reconciler.mjs \
|
|
250
|
+
/usr/local/lib/sellable-agent/fly-cron-proof-exec.mjs \
|
|
195
251
|
/usr/local/lib/sellable-agent/soul-artifact-validator.mjs \
|
|
196
252
|
/opt/sellable/bin/hermes-memory-dirty \
|
|
197
253
|
/opt/sellable/bin/hermes-endpoint-cron \
|
|
@@ -203,7 +259,9 @@ RUN node --input-type=module -e \
|
|
|
203
259
|
|
|
204
260
|
LABEL io.sellable.runtime.kind="admin" \
|
|
205
261
|
io.sellable.runtime.listener="native-dedicated-slack" \
|
|
206
|
-
io.sellable.runtime.installers="build-only"
|
|
262
|
+
io.sellable.runtime.installers="build-only" \
|
|
263
|
+
io.sellable.runtime.default-profile-bundle-version="${DEFAULT_PROFILE_BUNDLE_VERSION}" \
|
|
264
|
+
io.sellable.runtime.default-profile-bundle-digest="${DEFAULT_PROFILE_BUNDLE_DIGEST}"
|
|
207
265
|
|
|
208
266
|
ENV SELLABLE_AGENT_ADMIN_RUNTIME=/usr/local/lib/sellable-agent/admin-runtime/admin-runtime.mjs
|
|
209
267
|
ENV SELLABLE_AGENT_PRODUCT_MCP_PACKAGE=${PRODUCT_MCP_PACKAGE}
|
|
@@ -212,6 +270,8 @@ ENV SELLABLE_AGENT_ADMIN_MCP_PACKAGE=${ADMIN_MCP_PACKAGE}
|
|
|
212
270
|
ENV SELLABLE_AGENT_ADMIN_INSTALL_PACKAGE=${ADMIN_INSTALL_PACKAGE}
|
|
213
271
|
ENV SELLABLE_AGENT_ADMIN_LOCKED_TREE_DIGEST=${ADMIN_LOCKED_TREE_DIGEST}
|
|
214
272
|
ENV SELLABLE_AGENT_ADMIN_ARTIFACT_DIGEST=${ADMIN_ARTIFACT_DIGEST}
|
|
273
|
+
ENV SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_VERSION=${DEFAULT_PROFILE_BUNDLE_VERSION}
|
|
274
|
+
ENV SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_DIGEST=${DEFAULT_PROFILE_BUNDLE_DIGEST}
|
|
215
275
|
ENV HOME=/opt/data
|
|
216
276
|
ENV HERMES_HOME=/opt/data
|
|
217
277
|
ENV HERMES_WRITE_SAFE_ROOT=/opt/data
|