@sellable/install 0.1.358 → 0.1.359-phase111.20260720052618

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.
Files changed (31) hide show
  1. package/bin/sellable-install.mjs +0 -2
  2. package/package.json +2 -11
  3. package/skill-templates/refill-sends-evergreen.md +5 -0
  4. package/skill-templates/refill-sends-v2.md +23 -1
  5. package/skill-templates/refill-sends.md +14 -1
  6. package/bin/sellable-agent-egress-proxy.mjs +0 -31
  7. package/bin/sellable-agent-host-bootstrap.mjs +0 -26
  8. package/bin/sellable-agent-host-worker.mjs +0 -9
  9. package/bin/sellable-agent-runtime-helper.mjs +0 -37
  10. package/bin/sellable-agent-runtime-launcher.mjs +0 -151
  11. package/bin/sellable-agent-runtime-probe.mjs +0 -163
  12. package/bin/sellable-agent-sandbox-init.mjs +0 -93
  13. package/container/Dockerfile +0 -37
  14. package/container/README.md +0 -15
  15. package/container/compose.yaml +0 -22
  16. package/container/entrypoint.sh +0 -13
  17. package/lib/sellable-agent/containment-contract.mjs +0 -472
  18. package/lib/sellable-agent/external-runtime-builder.mjs +0 -272
  19. package/lib/sellable-agent/hermes-bridge.mjs +0 -497
  20. package/lib/sellable-agent/host-bootstrap.mjs +0 -458
  21. package/lib/sellable-agent/host-worker.mjs +0 -2067
  22. package/lib/sellable-agent/profile-materializer.mjs +0 -1410
  23. package/lib/sellable-agent/provisioning-adapter.mjs +0 -992
  24. package/lib/sellable-agent/runtime-boundary.mjs +0 -635
  25. package/lib/sellable-agent/runtime-egress-proxy.mjs +0 -241
  26. package/lib/sellable-agent/runtime-helper.mjs +0 -1428
  27. package/lib/sellable-agent/runtime-reconciler.mjs +0 -683
  28. package/lib/sellable-agent/service-installer.mjs +0 -441
  29. package/services/s6/sellable-agent-egress-proxy/run +0 -15
  30. package/services/s6/sellable-agent-host-worker/run +0 -4
  31. package/services/s6/sellable-agent-runtime/run +0 -19
@@ -1,1410 +0,0 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import {
3
- chmodSync,
4
- chownSync,
5
- closeSync,
6
- existsSync,
7
- fsyncSync,
8
- lstatSync,
9
- mkdirSync,
10
- openSync,
11
- readFileSync,
12
- readdirSync,
13
- realpathSync,
14
- renameSync,
15
- rmSync,
16
- writeFileSync,
17
- } from "node:fs";
18
- import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
19
- import { HERMES_AGENT_BRIDGE_CONTRACT } from "./hermes-bridge.mjs";
20
-
21
- export const PROFILE_MATERIALIZER_PORTS = Object.freeze({
22
- hostAppSecret: ["resolveHostSecret"],
23
- workspaceBotSecret: ["decryptWorkspaceSecret"],
24
- credential: ["install", "cleanup"],
25
- gateway: ["start", "stop", "observe"],
26
- containment: ["probe"],
27
- mcp: ["discoverTools", "safeRead"],
28
- ownership: ["apply", "observe", "remove"],
29
- });
30
-
31
- export const AGENT_PROFILE_INVENTORY = Object.freeze([
32
- "agent-profile.json",
33
- "config.yaml",
34
- "sellable/mcp.json",
35
- "SOUL.md",
36
- "skills/sellable/SKILL.md",
37
- ]);
38
-
39
- const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
40
- const SHA256 = /^[a-f0-9]{64}$/;
41
- const FINGERPRINT = /^[a-f0-9]{16}$/;
42
- const PRIVATE_REFERENCE = /^(?:secret|oauth-private):\/\/[A-Za-z0-9/_-]+$/;
43
- const RAW_SECRET = /(?:^|[^A-Za-z0-9])(?:xapp|xoxb|xoxa|sat)_[A-Za-z0-9_-]+|(?:xapp|xoxb|xoxa)-[A-Za-z0-9_-]+|oauth-provider-secret-byte|asec\.v1\./i;
44
-
45
- function sha256(value) {
46
- return createHash("sha256").update(value).digest("hex");
47
- }
48
-
49
- function stableJson(value) {
50
- if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
51
- if (value && typeof value === "object") {
52
- return `{${Object.keys(value)
53
- .sort()
54
- .map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
55
- .join(",")}}`;
56
- }
57
- return JSON.stringify(value);
58
- }
59
-
60
- function exactKeys(value, keys) {
61
- return (
62
- value &&
63
- typeof value === "object" &&
64
- !Array.isArray(value) &&
65
- Object.keys(value).sort().join("\0") === [...keys].sort().join("\0")
66
- );
67
- }
68
-
69
- function isInside(root, target) {
70
- const path = relative(root, target);
71
- return path === "" || (!path.startsWith("..") && !isAbsolute(path));
72
- }
73
-
74
- function privateRoot(pathValue) {
75
- try {
76
- if (!isAbsolute(pathValue) || !existsSync(pathValue)) return null;
77
- const link = lstatSync(pathValue);
78
- if (link.isSymbolicLink() || !link.isDirectory() || (link.mode & 0o077) !== 0) return null;
79
- return realpathSync(pathValue);
80
- } catch {
81
- return null;
82
- }
83
- }
84
-
85
- function traversableProfilesRoot(pathValue) {
86
- try {
87
- if (!isAbsolute(pathValue) || !existsSync(pathValue)) return null;
88
- const link = lstatSync(pathValue);
89
- const mode = link.mode & 0o777;
90
- if (
91
- link.isSymbolicLink() || !link.isDirectory() ||
92
- (mode & 0o700) !== 0o700 || (mode & 0o066) !== 0
93
- ) return null;
94
- return realpathSync(pathValue);
95
- } catch {
96
- return null;
97
- }
98
- }
99
-
100
- export function deriveContainedProfileId(workspaceId, agentId, hostId) {
101
- return `agent-${createHash("sha256")
102
- .update(`profile\0${workspaceId}\0${agentId}\0${hostId}`)
103
- .digest("hex")
104
- .slice(0, 24)}`;
105
- }
106
-
107
- const DESIRED_KEYS = [
108
- "workspaceId", "agentId", "hostId", "workerId", "installationId",
109
- "slackGeneration", "fence", "revisionId", "revisionSequence", "hermesCli", "hermesVersion",
110
- "installerPackage", "mcpPackage", "workspaceLockId", "toolInclude", "policyHash",
111
- "slackManifestHash", "slackAppHash", "slackTeamHash", "slackBotHash",
112
- "slackChannelHash", "slackDefaultChannelHash", "slackMemberAllowlistHash",
113
- "slackChannelIds", "slackDefaultChannelId", "slackMemberAllowlist",
114
- "soul", "soulHash", "skill", "skillsHash", "providerReference",
115
- "providerFingerprint", "serviceCredentialReference", "serviceCredentialFingerprint",
116
- "serviceCredentialGeneration", "xappReference", "xappFingerprint", "xoxbReference",
117
- "xoxbCiphertextFingerprint",
118
- ];
119
-
120
- function validateDesired(desired) {
121
- if (!exactKeys(desired, DESIRED_KEYS)) return { ok: false, code: "desired_schema_invalid" };
122
- for (const key of [
123
- "workspaceId", "agentId", "hostId", "workerId", "installationId", "revisionId",
124
- ]) {
125
- if (!SAFE_ID.test(desired[key])) return { ok: false, code: "desired_identity_invalid", field: key };
126
- }
127
- if (
128
- desired.workspaceLockId !== desired.workspaceId ||
129
- !/^[1-9][0-9]{0,19}$/.test(desired.fence) ||
130
- !Number.isSafeInteger(desired.revisionSequence) || desired.revisionSequence < 1 ||
131
- !Number.isSafeInteger(desired.slackGeneration) || desired.slackGeneration < 1 ||
132
- !Number.isSafeInteger(desired.serviceCredentialGeneration) || desired.serviceCredentialGeneration < 1 ||
133
- desired.hermesCli !== "hermes" || desired.hermesVersion !== "0.18.0" ||
134
- desired.installerPackage !== "@sellable/install@0.1.358" ||
135
- desired.mcpPackage !== "@sellable/mcp@0.1.619" ||
136
- !Array.isArray(desired.toolInclude) || desired.toolInclude.length === 0 ||
137
- desired.toolInclude.some((tool) => !/^[a-z][a-z0-9_]{0,127}$/.test(tool)) ||
138
- new Set(desired.toolInclude).size !== desired.toolInclude.length ||
139
- JSON.stringify([...desired.toolInclude].sort()) !== JSON.stringify(desired.toolInclude) ||
140
- !Array.isArray(desired.slackChannelIds) || desired.slackChannelIds.length === 0 ||
141
- desired.slackChannelIds.some((id) => !/^C[A-Z0-9]{8,20}$/.test(id)) ||
142
- JSON.stringify([...new Set(desired.slackChannelIds)].sort()) !== JSON.stringify(desired.slackChannelIds) ||
143
- !desired.slackChannelIds.includes(desired.slackDefaultChannelId) ||
144
- !Array.isArray(desired.slackMemberAllowlist) || desired.slackMemberAllowlist.length === 0 ||
145
- desired.slackMemberAllowlist.some((id) => !/^[UW][A-Z0-9]{8,20}$/.test(id)) ||
146
- JSON.stringify([...new Set(desired.slackMemberAllowlist)].sort()) !== JSON.stringify(desired.slackMemberAllowlist)
147
- ) {
148
- return { ok: false, code: "desired_runtime_invalid" };
149
- }
150
- for (const key of [
151
- "policyHash", "slackManifestHash", "slackAppHash", "slackTeamHash", "slackBotHash",
152
- "slackChannelHash", "slackDefaultChannelHash", "slackMemberAllowlistHash", "soulHash",
153
- "skillsHash", "providerFingerprint",
154
- ]) {
155
- if (!SHA256.test(desired[key])) return { ok: false, code: "desired_hash_invalid", field: key };
156
- }
157
- if (
158
- sha256(desired.soul) !== desired.soulHash ||
159
- sha256(desired.skill) !== desired.skillsHash ||
160
- sha256(stableJson(desired.slackChannelIds)) !== desired.slackChannelHash ||
161
- sha256(desired.slackDefaultChannelId) !== desired.slackDefaultChannelHash ||
162
- sha256(stableJson(desired.slackMemberAllowlist)) !== desired.slackMemberAllowlistHash ||
163
- !PRIVATE_REFERENCE.test(desired.providerReference) ||
164
- !PRIVATE_REFERENCE.test(desired.serviceCredentialReference) ||
165
- !PRIVATE_REFERENCE.test(desired.xappReference) ||
166
- !PRIVATE_REFERENCE.test(desired.xoxbReference) ||
167
- !FINGERPRINT.test(desired.serviceCredentialFingerprint) ||
168
- !FINGERPRINT.test(desired.xappFingerprint) ||
169
- !FINGERPRINT.test(desired.xoxbCiphertextFingerprint) ||
170
- RAW_SECRET.test(desired.soul) || RAW_SECRET.test(desired.skill)
171
- ) {
172
- return { ok: false, code: "desired_secret_or_content_invalid" };
173
- }
174
- return { ok: true };
175
- }
176
-
177
- const MANIFEST_KEYS = [
178
- "schemaVersion", "producer", "subject", "profileId", "workspaceId", "agentId",
179
- "hostId", "workerId", "installationId", "slackGeneration", "fence", "revisionId", "revisionSequence",
180
- "hermesCli", "hermesVersion", "installerPackage", "mcpPackage", "workspaceLockId",
181
- "toolInclude", "policyHash", "slackManifestHash", "slackAppHash", "slackTeamHash",
182
- "slackBotHash", "slackChannelHash", "slackDefaultChannelHash",
183
- "slackMemberAllowlistHash", "soulHash", "skillsHash", "providerReference",
184
- "slackChannelIds", "slackDefaultChannelId", "slackMemberAllowlist",
185
- "providerFingerprint", "serviceCredentialReference", "serviceCredentialFingerprint",
186
- "serviceCredentialGeneration", "xappReference", "xappFingerprint", "xoxbReference",
187
- "xoxbCiphertextFingerprint", "fileHashes", "profileDigest",
188
- ];
189
-
190
- function manifestBody(desired) {
191
- const profileId = deriveContainedProfileId(desired.workspaceId, desired.agentId, desired.hostId);
192
- return {
193
- schemaVersion: "sellable-agent-profile/v1",
194
- producer: "sellable-agent-profile-materializer",
195
- subject: `agent-profile:${profileId}`,
196
- profileId,
197
- workspaceId: desired.workspaceId,
198
- agentId: desired.agentId,
199
- hostId: desired.hostId,
200
- workerId: desired.workerId,
201
- installationId: desired.installationId,
202
- slackGeneration: desired.slackGeneration,
203
- fence: desired.fence,
204
- revisionId: desired.revisionId,
205
- revisionSequence: desired.revisionSequence,
206
- hermesCli: desired.hermesCli,
207
- hermesVersion: desired.hermesVersion,
208
- installerPackage: desired.installerPackage,
209
- mcpPackage: desired.mcpPackage,
210
- workspaceLockId: desired.workspaceLockId,
211
- toolInclude: [...desired.toolInclude],
212
- policyHash: desired.policyHash,
213
- slackManifestHash: desired.slackManifestHash,
214
- slackAppHash: desired.slackAppHash,
215
- slackTeamHash: desired.slackTeamHash,
216
- slackBotHash: desired.slackBotHash,
217
- slackChannelHash: desired.slackChannelHash,
218
- slackDefaultChannelHash: desired.slackDefaultChannelHash,
219
- slackMemberAllowlistHash: desired.slackMemberAllowlistHash,
220
- slackChannelIds: [...desired.slackChannelIds],
221
- slackDefaultChannelId: desired.slackDefaultChannelId,
222
- slackMemberAllowlist: [...desired.slackMemberAllowlist],
223
- soulHash: desired.soulHash,
224
- skillsHash: desired.skillsHash,
225
- providerReference: desired.providerReference,
226
- providerFingerprint: desired.providerFingerprint,
227
- serviceCredentialReference: desired.serviceCredentialReference,
228
- serviceCredentialFingerprint: desired.serviceCredentialFingerprint,
229
- serviceCredentialGeneration: desired.serviceCredentialGeneration,
230
- xappReference: desired.xappReference,
231
- xappFingerprint: desired.xappFingerprint,
232
- xoxbReference: desired.xoxbReference,
233
- xoxbCiphertextFingerprint: desired.xoxbCiphertextFingerprint,
234
- };
235
- }
236
-
237
- export function buildAgentProfileManifest(desired) {
238
- const valid = validateDesired(desired);
239
- if (!valid.ok) return valid;
240
- return { ok: true, manifest: manifestBody(desired) };
241
- }
242
-
243
- function configFor(desired, profileId) {
244
- return {
245
- profile: profileId,
246
- hermes: { cli: desired.hermesCli, version: desired.hermesVersion },
247
- slack: {
248
- appTokenFile: `/run/sellable-agent/${profileId}/slack-app`,
249
- botTokenFile: `/run/sellable-agent/${profileId}/slack-bot`,
250
- generation: desired.slackGeneration,
251
- manifestHash: desired.slackManifestHash,
252
- appHash: desired.slackAppHash,
253
- teamHash: desired.slackTeamHash,
254
- botHash: desired.slackBotHash,
255
- channelHash: desired.slackChannelHash,
256
- defaultChannelHash: desired.slackDefaultChannelHash,
257
- memberAllowlistHash: desired.slackMemberAllowlistHash,
258
- selectedChannelIds: [...desired.slackChannelIds],
259
- defaultChannelId: desired.slackDefaultChannelId,
260
- memberAllowlist: [...desired.slackMemberAllowlist],
261
- },
262
- mcp_servers: {
263
- sellable: {
264
- command: "/usr/local/lib/sellable-agent/external-runtime/mcp/bin/sellable-mcp",
265
- args: [],
266
- enabled: true,
267
- env: {
268
- SELLABLE_CONFIG_PATH: `/runtime/profiles/${profileId}/sellable/mcp.json`,
269
- SELLABLE_LOCK_WORKSPACE_ID: desired.workspaceId,
270
- SELLABLE_REQUIRE_WORKSPACE_LOCK: "1",
271
- },
272
- },
273
- },
274
- tools: { include: [...desired.toolInclude] },
275
- provider: { reference: desired.providerReference, fingerprint: desired.providerFingerprint },
276
- };
277
- }
278
-
279
- function mcpFor(desired, profileId) {
280
- return {
281
- package: desired.mcpPackage,
282
- workspaceLockId: desired.workspaceLockId,
283
- requireWorkspaceLock: true,
284
- tools: { include: [...desired.toolInclude] },
285
- policyHash: desired.policyHash,
286
- serviceCredential: {
287
- reference: desired.serviceCredentialReference,
288
- fingerprint: desired.serviceCredentialFingerprint,
289
- generation: desired.serviceCredentialGeneration,
290
- file: `/run/sellable-agent/${profileId}/service-credential`,
291
- },
292
- agentContext: {
293
- workspaceId: desired.workspaceId,
294
- agentId: desired.agentId,
295
- generation: desired.serviceCredentialGeneration,
296
- policyHash: desired.policyHash,
297
- },
298
- requestChannelPolicy: {
299
- source: "_meta.sellableAgent.channelId",
300
- selectedChannelIds: [...desired.slackChannelIds],
301
- defaultChannelId: desired.slackDefaultChannelId,
302
- memberAllowlist: [...desired.slackMemberAllowlist],
303
- },
304
- };
305
- }
306
-
307
- function renderedProfileFiles(desired) {
308
- const profileId = deriveContainedProfileId(desired.workspaceId, desired.agentId, desired.hostId);
309
- return {
310
- "config.yaml": `${JSON.stringify(configFor(desired, profileId), null, 2)}\n`,
311
- "sellable/mcp.json": `${JSON.stringify(mcpFor(desired, profileId), null, 2)}\n`,
312
- "SOUL.md": desired.soul,
313
- "skills/sellable/SKILL.md": desired.skill,
314
- };
315
- }
316
-
317
- export function computeAgentProfileDigest(desired) {
318
- const valid = validateDesired(desired);
319
- if (!valid.ok) return valid;
320
- const files = renderedProfileFiles(desired);
321
- const fileHashes = Object.fromEntries(
322
- Object.keys(files).sort().map((logicalPath) => [logicalPath, sha256(files[logicalPath])])
323
- );
324
- return {
325
- ok: true,
326
- fileHashes,
327
- profileDigest: sha256(stableJson({ ...manifestBody(desired), fileHashes })),
328
- };
329
- }
330
-
331
- function atomicWrite(pathValue, bytes, mode) {
332
- mkdirSync(dirname(pathValue), { recursive: true, mode: 0o700 });
333
- const temp = join(dirname(pathValue), `.${basename(pathValue)}.${randomUUID()}.tmp`);
334
- writeFileSync(temp, bytes, { mode, flag: "wx" });
335
- chmodSync(temp, mode);
336
- const descriptor = openSync(temp, "r");
337
- try { fsyncSync(descriptor); } finally { closeSync(descriptor); }
338
- renameSync(temp, pathValue);
339
- }
340
-
341
- function fsyncDirectory(pathValue) {
342
- const descriptor = openSync(pathValue, "r");
343
- try { fsyncSync(descriptor); } finally { closeSync(descriptor); }
344
- }
345
-
346
- function renderStagedProfile(stageRoot, desired) {
347
- mkdirSync(stageRoot, { recursive: true, mode: 0o700 });
348
- const files = renderedProfileFiles(desired);
349
- for (const [logicalPath, content] of Object.entries(files)) {
350
- atomicWrite(join(stageRoot, logicalPath), content, logicalPath.endsWith(".md") ? 0o444 : 0o600);
351
- }
352
- const fileHashes = Object.fromEntries(
353
- Object.keys(files).sort().map((logicalPath) => [logicalPath, sha256(readFileSync(join(stageRoot, logicalPath)))])
354
- );
355
- const base = manifestBody(desired);
356
- const profileDigest = sha256(stableJson({ ...base, fileHashes }));
357
- const manifest = { ...base, fileHashes, profileDigest };
358
- atomicWrite(join(stageRoot, "agent-profile.json"), `${JSON.stringify(manifest, null, 2)}\n`, 0o600);
359
- fsyncDirectory(stageRoot);
360
- return manifest;
361
- }
362
-
363
- function inventory(root) {
364
- const files = [];
365
- const walk = (pathValue, relativePath) => {
366
- const link = lstatSync(pathValue);
367
- if (link.isSymbolicLink()) throw new Error("profile symlink rejected");
368
- if (link.isDirectory()) {
369
- for (const name of readdirSync(pathValue).sort()) {
370
- walk(join(pathValue, name), relativePath ? `${relativePath}/${name}` : name);
371
- }
372
- return;
373
- }
374
- if (!link.isFile()) throw new Error("profile entry rejected");
375
- files.push(relativePath);
376
- };
377
- walk(root, "");
378
- return files.sort();
379
- }
380
-
381
- function treeInventory(root) {
382
- const entries = [];
383
- const walk = (pathValue, relativePath) => {
384
- const link = lstatSync(pathValue);
385
- if (link.isSymbolicLink()) throw new Error("profile symlink rejected");
386
- entries.push({
387
- path: relativePath || ".",
388
- type: link.isDirectory() ? "directory" : link.isFile() ? "file" : "other",
389
- uid: link.uid,
390
- gid: link.gid,
391
- mode: link.mode & 0o777,
392
- });
393
- if (link.isDirectory()) {
394
- for (const name of readdirSync(pathValue).sort()) {
395
- walk(join(pathValue, name), relativePath ? `${relativePath}/${name}` : name);
396
- }
397
- }
398
- };
399
- walk(root, "");
400
- return entries;
401
- }
402
-
403
- export function createPosixProfileOwnershipPort() {
404
- return {
405
- async apply({ profileRoot, uid, gid }) {
406
- const entries = treeInventory(profileRoot).sort((a, b) => b.path.length - a.path.length);
407
- for (const entry of entries) {
408
- const pathValue = entry.path === "." ? profileRoot : join(profileRoot, entry.path);
409
- chownSync(pathValue, uid, gid);
410
- chmodSync(pathValue, entry.type === "directory" ? 0o500 : 0o400);
411
- }
412
- return { ok: true };
413
- },
414
- async observe({ profileRoot }) {
415
- return { ok: true, entries: treeInventory(profileRoot) };
416
- },
417
- async remove({ profileRoot }) {
418
- if (existsSync(profileRoot)) rmSync(profileRoot, { recursive: true, force: true });
419
- return { ok: true };
420
- },
421
- };
422
- }
423
-
424
- function validateRuntimeOwnership(observed, runtimeIdentity) {
425
- if (!observed?.ok || !Array.isArray(observed.entries)) return false;
426
- const expectedPaths = [
427
- ".", "agent-profile.json", "config.yaml", "sellable", "sellable/mcp.json",
428
- "SOUL.md", "skills", "skills/sellable", "skills/sellable/SKILL.md",
429
- ].sort();
430
- if (stableJson(observed.entries.map((entry) => entry.path).sort()) !== stableJson(expectedPaths)) {
431
- return false;
432
- }
433
- return observed.entries.every(
434
- (entry) =>
435
- entry.uid === runtimeIdentity.uid &&
436
- entry.gid === runtimeIdentity.gid &&
437
- (entry.type === "directory"
438
- ? entry.mode === 0o500
439
- : entry.type === "file" && entry.mode === 0o400)
440
- );
441
- }
442
-
443
- export function validateAgentProfileBundle({ profileRoot, desired }) {
444
- const valid = validateDesired(desired);
445
- if (!valid.ok) return valid;
446
- try {
447
- if (!isAbsolute(profileRoot) || !existsSync(profileRoot) || lstatSync(profileRoot).isSymbolicLink()) {
448
- return { ok: false, code: "profile_path_invalid" };
449
- }
450
- const foundInventory = inventory(profileRoot);
451
- if (JSON.stringify(foundInventory) !== JSON.stringify([...AGENT_PROFILE_INVENTORY].sort())) {
452
- return { ok: false, code: "profile_inventory_open" };
453
- }
454
- const manifestPath = join(profileRoot, "agent-profile.json");
455
- const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
456
- if (!exactKeys(manifest, MANIFEST_KEYS)) return { ok: false, code: "manifest_schema_invalid" };
457
- const expectedBody = manifestBody(desired);
458
- if (Object.entries(expectedBody).some(([key, value]) => stableJson(manifest[key]) !== stableJson(value))) {
459
- return { ok: false, code: "manifest_identity_drift" };
460
- }
461
- const expectedHashes = Object.fromEntries(
462
- AGENT_PROFILE_INVENTORY.filter((path) => path !== "agent-profile.json")
463
- .sort()
464
- .map((logicalPath) => [logicalPath, sha256(readFileSync(join(profileRoot, logicalPath)))])
465
- );
466
- if (stableJson(manifest.fileHashes) !== stableJson(expectedHashes)) {
467
- return { ok: false, code: "profile_file_hash_drift" };
468
- }
469
- if (manifest.profileDigest !== sha256(stableJson({ ...expectedBody, fileHashes: expectedHashes }))) {
470
- return { ok: false, code: "profile_digest_drift" };
471
- }
472
- const config = JSON.parse(readFileSync(join(profileRoot, "config.yaml"), "utf8"));
473
- const mcp = JSON.parse(readFileSync(join(profileRoot, "sellable", "mcp.json"), "utf8"));
474
- if (stableJson(config) !== stableJson(configFor(desired, manifest.profileId))) {
475
- return { ok: false, code: "config_drift" };
476
- }
477
- if (stableJson(mcp) !== stableJson(mcpFor(desired, manifest.profileId))) {
478
- return { ok: false, code: "mcp_policy_drift" };
479
- }
480
- if (
481
- readFileSync(join(profileRoot, "SOUL.md"), "utf8") !== desired.soul ||
482
- readFileSync(join(profileRoot, "skills", "sellable", "SKILL.md"), "utf8") !== desired.skill
483
- ) return { ok: false, code: "content_drift" };
484
- for (const logicalPath of foundInventory) {
485
- const pathValue = join(profileRoot, logicalPath);
486
- const link = lstatSync(pathValue);
487
- if ((link.mode & 0o022) !== 0 || RAW_SECRET.test(readFileSync(pathValue, "utf8"))) {
488
- return { ok: false, code: "secret_or_mode_drift" };
489
- }
490
- }
491
- return { ok: true, manifest, hashes: { ...expectedHashes, profile: manifest.profileDigest } };
492
- } catch {
493
- return { ok: false, code: "profile_validation_failed" };
494
- }
495
- }
496
-
497
- function secretBinding(desired, servicePurpose) {
498
- return {
499
- workspaceId: desired.workspaceId,
500
- agentId: desired.agentId,
501
- installationId: desired.installationId,
502
- generation: desired.slackGeneration,
503
- hostId: desired.hostId,
504
- workerId: desired.workerId,
505
- fence: desired.fence,
506
- servicePurpose,
507
- };
508
- }
509
-
510
- function sameBinding(value, expected) {
511
- return exactKeys(value, Object.keys(expected)) &&
512
- Object.entries(expected).every(([key, expectedValue]) => value[key] === expectedValue);
513
- }
514
-
515
- function removeDirect(root, pathValue) {
516
- const realRoot = realpathSync(root);
517
- const resolved = resolve(pathValue);
518
- if (dirname(resolved) !== realRoot || !isInside(realRoot, resolved)) throw new Error("path confinement failed");
519
- if (existsSync(resolved)) {
520
- if (lstatSync(resolved).isSymbolicLink()) throw new Error("symlink removal rejected");
521
- rmSync(resolved, { recursive: true, force: true });
522
- }
523
- }
524
-
525
- function cleanupSecrets(runtimeSecretsRoot, profileId) {
526
- try {
527
- const root = realpathSync(runtimeSecretsRoot);
528
- const target = join(root, profileId);
529
- if (dirname(target) === root && existsSync(target) && !lstatSync(target).isSymbolicLink()) {
530
- rmSync(target, { recursive: true, force: true });
531
- }
532
- } catch {}
533
- }
534
-
535
- async function materializeRuntimeSecrets({ desired, runtimeSecretsRoot, ports, serviceCredential, runtimeIdentity }) {
536
- const profileId = deriveContainedProfileId(desired.workspaceId, desired.agentId, desired.hostId);
537
- try {
538
- const appBinding = secretBinding(desired, "slack-app");
539
- const xapp = await ports.hostAppSecret.resolveHostSecret({
540
- ...appBinding,
541
- reference: desired.xappReference,
542
- expectedFingerprint: desired.xappFingerprint,
543
- });
544
- if (
545
- !sameBinding(xapp?.binding, appBinding) ||
546
- typeof xapp?.secret !== "string" ||
547
- !xapp.secret ||
548
- xapp.fingerprint !== desired.xappFingerprint ||
549
- sha256(xapp.secret).slice(0, 16) !== desired.xappFingerprint
550
- ) return { ok: false, code: "secret_binding_rejected" };
551
-
552
- const botBinding = secretBinding(desired, "slack-bot");
553
- const xoxb = await ports.workspaceBotSecret.decryptWorkspaceSecret({
554
- ...botBinding,
555
- reference: desired.xoxbReference,
556
- ciphertextFingerprint: desired.xoxbCiphertextFingerprint,
557
- });
558
- if (
559
- !sameBinding(xoxb?.binding, botBinding) ||
560
- typeof xoxb?.secret !== "string" ||
561
- !xoxb.secret ||
562
- xoxb.generation !== desired.slackGeneration ||
563
- xoxb.fingerprint !== desired.xoxbCiphertextFingerprint ||
564
- xoxb.fingerprint !== sha256(xoxb.secret).slice(0, 16)
565
- ) return { ok: false, code: "secret_binding_rejected" };
566
-
567
- const credentialBinding = {
568
- workspaceId: desired.workspaceId,
569
- agentId: desired.agentId,
570
- installationId: desired.installationId,
571
- generation: desired.serviceCredentialGeneration,
572
- hostId: desired.hostId,
573
- workerId: desired.workerId,
574
- fence: desired.fence,
575
- servicePurpose: "mcp-service",
576
- };
577
- if (
578
- !sameBinding(serviceCredential?.binding, credentialBinding) ||
579
- typeof serviceCredential?.secret !== "string" ||
580
- !serviceCredential.secret.startsWith("sat_") ||
581
- serviceCredential.generation !== desired.serviceCredentialGeneration ||
582
- serviceCredential.fingerprint !== desired.serviceCredentialFingerprint ||
583
- sha256(serviceCredential.secret).slice(0, 16) !== desired.serviceCredentialFingerprint
584
- ) return { ok: false, code: "secret_binding_rejected" };
585
-
586
- const credential = await ports.credential.install({
587
- desired,
588
- profileId,
589
- runtimeIdentity,
590
- serviceCredential,
591
- });
592
- if (
593
- !credential?.ok || credential.profileId !== profileId ||
594
- credential.fingerprint !== desired.serviceCredentialFingerprint ||
595
- credential.generation !== desired.serviceCredentialGeneration ||
596
- typeof credential.path !== "string" || !isAbsolute(credential.path)
597
- ) return { ok: false, code: "service_credential_install_rejected" };
598
-
599
- const root = realpathSync(runtimeSecretsRoot);
600
- const serviceRoot = join(root, profileId);
601
- if (dirname(serviceRoot) !== root) return { ok: false, code: "secret_path_rejected" };
602
- cleanupSecrets(root, profileId);
603
- mkdirSync(serviceRoot, { recursive: false, mode: 0o700 });
604
- const appPath = join(serviceRoot, "slack-app");
605
- const botPath = join(serviceRoot, "slack-bot");
606
- atomicWrite(appPath, `${xapp.secret}\n`, 0o600);
607
- atomicWrite(botPath, `${xoxb.secret}\n`, 0o600);
608
- fsyncDirectory(serviceRoot);
609
- const workerUid = process.getuid?.() ?? lstatSync(serviceRoot).uid;
610
- const workerGid = process.getgid?.() ?? lstatSync(serviceRoot).gid;
611
- if (
612
- lstatSync(serviceRoot).uid !== workerUid ||
613
- lstatSync(serviceRoot).gid !== workerGid ||
614
- (lstatSync(serviceRoot).mode & 0o777) !== 0o700 ||
615
- [appPath, botPath].some((pathValue) => {
616
- const link = lstatSync(pathValue);
617
- return link.isSymbolicLink() || !link.isFile() || link.uid !== workerUid ||
618
- link.gid !== workerGid || (link.mode & 0o777) !== 0o600;
619
- })
620
- ) throw new Error("worker secret ownership mismatch");
621
- return {
622
- ok: true,
623
- files: [appPath, botPath, credential.path],
624
- xappFingerprint: xapp.fingerprint,
625
- xoxbFingerprint: xoxb.fingerprint,
626
- serviceCredentialFingerprint: serviceCredential.fingerprint,
627
- serviceCredentialGeneration: serviceCredential.generation,
628
- };
629
- } catch {
630
- cleanupSecrets(runtimeSecretsRoot, profileId);
631
- try { await ports.credential.cleanup({ profileId }); } catch {}
632
- return { ok: false, code: "secret_materialization_failed" };
633
- }
634
- }
635
-
636
- function reuseInstalledRuntimeSecrets({ desired, runtimeSecretsRoot, mcpCredentialRoot, observation, runtimeIdentity }) {
637
- const profileId = deriveContainedProfileId(desired.workspaceId, desired.agentId, desired.hostId);
638
- try {
639
- if (
640
- observation?.profileId !== profileId || observation.workspaceId !== desired.workspaceId ||
641
- observation.agentId !== desired.agentId || observation.slack?.xappPresent !== true ||
642
- observation.slack?.xoxbPresent !== true || observation.serviceCredential?.present !== true ||
643
- observation.slack.xappFingerprint !== desired.xappFingerprint ||
644
- observation.slack.xoxbFingerprint !== desired.xoxbCiphertextFingerprint ||
645
- observation.slack.generation !== desired.slackGeneration ||
646
- observation.serviceCredential.fingerprint !== desired.serviceCredentialFingerprint ||
647
- observation.serviceCredential.generation !== desired.serviceCredentialGeneration
648
- ) return { ok: false, code: "installed_secret_identity_rejected" };
649
- const slackRoot = privateRoot(runtimeSecretsRoot);
650
- const mcpRoot = resolve(mcpCredentialRoot ?? "");
651
- if (!slackRoot || !isAbsolute(mcpRoot)) return { ok: false, code: "installed_secret_root_rejected" };
652
- const slackProfileRoot = join(slackRoot, profileId);
653
- const mcpProfileRoot = join(mcpRoot, profileId);
654
- const files = [
655
- join(slackProfileRoot, "slack-app"),
656
- join(slackProfileRoot, "slack-bot"),
657
- join(mcpProfileRoot, "service-credential"),
658
- ];
659
- for (const [index, pathValue] of files.slice(0, 2).entries()) {
660
- const link = lstatSync(pathValue);
661
- const expectedUid = process.getuid?.() ?? link.uid;
662
- const expectedGid = process.getgid?.() ?? link.gid;
663
- if (
664
- link.isSymbolicLink() || !link.isFile() || (link.mode & 0o777) !== 0o600 ||
665
- link.uid !== expectedUid || link.gid !== expectedGid
666
- ) throw new Error("installed secret file rejected");
667
- }
668
- return {
669
- ok: true,
670
- reused: true,
671
- files,
672
- xappFingerprint: observation.slack.xappFingerprint,
673
- xoxbFingerprint: observation.slack.xoxbFingerprint,
674
- serviceCredentialFingerprint: observation.serviceCredential.fingerprint,
675
- serviceCredentialGeneration: observation.serviceCredential.generation,
676
- };
677
- } catch {
678
- return { ok: false, code: "installed_secret_readback_rejected" };
679
- }
680
- }
681
-
682
- function portsValid(ports) {
683
- return Object.entries(PROFILE_MATERIALIZER_PORTS).every(([group, methods]) =>
684
- ports?.[group] && methods.every((method) => typeof ports[group][method] === "function")
685
- );
686
- }
687
-
688
- function expectedStaticObservation({ desired, profileId, validation, secrets }) {
689
- return {
690
- profileId,
691
- workspaceId: desired.workspaceId,
692
- agentId: desired.agentId,
693
- revisionId: desired.revisionId,
694
- hashes: {
695
- package: sha256(`${desired.installerPackage}\0${desired.mcpPackage}\0${desired.hermesVersion}\0${HERMES_AGENT_BRIDGE_CONTRACT.contractDigest}`),
696
- config: validation.hashes["config.yaml"],
697
- mcp: validation.hashes["sellable/mcp.json"],
698
- content: validation.hashes.profile,
699
- policy: desired.policyHash,
700
- slackManifest: desired.slackManifestHash,
701
- soul: desired.soulHash,
702
- skills: desired.skillsHash,
703
- provider: desired.providerFingerprint,
704
- },
705
- slack: {
706
- app: desired.slackAppHash,
707
- team: desired.slackTeamHash,
708
- bot: desired.slackBotHash,
709
- channels: desired.slackChannelHash,
710
- defaultChannel: desired.slackDefaultChannelHash,
711
- memberAllowlist: desired.slackMemberAllowlistHash,
712
- channelIds: [...desired.slackChannelIds],
713
- defaultChannelId: desired.slackDefaultChannelId,
714
- memberIds: [...desired.slackMemberAllowlist],
715
- generation: desired.slackGeneration,
716
- xappPresent: true,
717
- xoxbPresent: true,
718
- xappFingerprint: secrets.xappFingerprint,
719
- xoxbFingerprint: secrets.xoxbFingerprint,
720
- },
721
- serviceCredential: {
722
- present: true,
723
- fingerprint: secrets.serviceCredentialFingerprint,
724
- generation: secrets.serviceCredentialGeneration,
725
- },
726
- hermesBridge: {
727
- bridgeId: HERMES_AGENT_BRIDGE_CONTRACT.bridgeId,
728
- contractDigest: HERMES_AGENT_BRIDGE_CONTRACT.contractDigest,
729
- hermesVersion: HERMES_AGENT_BRIDGE_CONTRACT.hermesVersion,
730
- state: "installed",
731
- reasons: [],
732
- files: HERMES_AGENT_BRIDGE_CONTRACT.files.map((file) => ({ path: file.path, sha256: file.patchedSha256 })),
733
- status: "verified",
734
- },
735
- };
736
- }
737
-
738
- function validateIndependentObservation(observed, expected, runtimeIdentity) {
739
- if (!observed || !exactKeys(observed, [...Object.keys(expected), "process"])) return false;
740
- if (!exactKeys(observed.process, ["ready", "pid", "startTicks", "bootId", "serviceUser", "uid"])) {
741
- return false;
742
- }
743
- const { process, ...staticObserved } = observed;
744
- const observedPackageHash = staticObserved.hashes?.package;
745
- const expectedWithoutPackage = {
746
- ...expected,
747
- hashes: { ...expected.hashes, package: undefined },
748
- };
749
- const observedWithoutPackage = {
750
- ...staticObserved,
751
- hashes: { ...staticObserved.hashes, package: undefined },
752
- };
753
- return (
754
- SHA256.test(observedPackageHash ?? "") &&
755
- stableJson(observedWithoutPackage) === stableJson(expectedWithoutPackage) &&
756
- process.ready === true &&
757
- Number.isSafeInteger(process.pid) && process.pid > 1 &&
758
- /^[1-9][0-9]{0,31}$/.test(process.startTicks ?? "") &&
759
- typeof process.bootId === "string" && /^[A-Za-z0-9_-]{1,128}$/.test(process.bootId) &&
760
- process.serviceUser === runtimeIdentity.user &&
761
- process.uid === runtimeIdentity.uid
762
- );
763
- }
764
-
765
- const PROMOTION_TRANSACTION_VERSION = "sellable-agent-profile-promotion/v1";
766
- const PROMOTION_SIGNATURE = /^[A-Za-z0-9_-]{64,512}$/;
767
-
768
- function promotionTransactionPath(profilesRoot, profileId) {
769
- return join(profilesRoot, `.${profileId}.promotion.json`);
770
- }
771
-
772
- function promotionJournal(input) {
773
- const journal = input.promotionJournal ?? input.ports?.promotionJournal;
774
- const operationId = input.operationId ?? journal?.operationId;
775
- const effectId = input.effectId ?? journal?.effectId;
776
- if (
777
- !journal || typeof journal.sign !== "function" || typeof journal.verify !== "function" ||
778
- !Number.isSafeInteger(journal.signerGeneration) || journal.signerGeneration < 1 ||
779
- !SAFE_ID.test(operationId ?? "") || !/^sahe_[a-f0-9]{64}$/.test(effectId ?? "")
780
- ) throw new Error("promotion journal authority rejected");
781
- return { journal, operationId, effectId };
782
- }
783
-
784
- function promotionSignedBody(transaction) {
785
- const { signature: _signature, ...body } = transaction;
786
- return body;
787
- }
788
-
789
- function signPromotionTransaction(transaction, authority) {
790
- const signature = authority.journal.sign(promotionSignedBody(transaction));
791
- if (!PROMOTION_SIGNATURE.test(signature ?? "")) throw new Error("promotion signature rejected");
792
- return { ...transaction, signature };
793
- }
794
-
795
- function promotionObservationCore(observation) {
796
- const {
797
- profileDigest: _profileDigest,
798
- tools: _tools,
799
- revisionSequence: _revisionSequence,
800
- ...core
801
- } = observation ?? {};
802
- return core;
803
- }
804
-
805
- function targetObservationDigest(observation, transaction) {
806
- const core = promotionObservationCore(observation);
807
- if (
808
- core.profileId !== transaction.profileId || core.revisionId !== transaction.targetRevisionId ||
809
- core.hashes?.content !== transaction.targetProfileDigest || !SHA256.test(core.hashes?.package ?? "") ||
810
- !core.process?.ready || !Number.isSafeInteger(core.process?.pid) || core.process.pid < 2 ||
811
- !/^[1-9][0-9]{0,31}$/.test(core.process?.startTicks ?? "")
812
- ) throw new Error("promotion target observation rejected");
813
- return sha256(stableJson(core));
814
- }
815
-
816
- function priorObservationCore(observation) {
817
- const {
818
- process: _process,
819
- revisionSequence: _revisionSequence,
820
- tools: _tools,
821
- ...core
822
- } = observation ?? {};
823
- return core;
824
- }
825
-
826
- function validPriorObservation(observation, profileId) {
827
- return Boolean(
828
- observation && observation.profileId === profileId && SAFE_ID.test(observation.workspaceId ?? "") &&
829
- SAFE_ID.test(observation.agentId ?? "") && SAFE_ID.test(observation.revisionId ?? "") &&
830
- SHA256.test(observation.hashes?.content ?? "") &&
831
- FINGERPRINT.test(observation.slack?.xappFingerprint ?? "") &&
832
- FINGERPRINT.test(observation.slack?.xoxbFingerprint ?? "") &&
833
- FINGERPRINT.test(observation.serviceCredential?.fingerprint ?? "") &&
834
- Number.isSafeInteger(observation.serviceCredential?.generation) &&
835
- observation.serviceCredential.generation > 0 &&
836
- !RAW_SECRET.test(JSON.stringify(observation))
837
- );
838
- }
839
-
840
- function writePromotionTransaction({
841
- profilesRoot, profileId, desired, targetProfileDigest, reusedSecrets, priorObservation, authority,
842
- }) {
843
- const transaction = signPromotionTransaction({
844
- version: PROMOTION_TRANSACTION_VERSION,
845
- status: "PREPARED",
846
- profileId,
847
- operationId: authority.operationId,
848
- effectId: authority.effectId,
849
- signerGeneration: authority.journal.signerGeneration,
850
- targetRevisionId: desired.revisionId,
851
- targetRevisionSequence: desired.revisionSequence,
852
- targetProfileDigest,
853
- fence: desired.fence,
854
- reusedSecrets,
855
- priorObservation: reusedSecrets ? priorObservation : null,
856
- targetObservationDigest: null,
857
- }, authority);
858
- if (reusedSecrets && !validPriorObservation(priorObservation, profileId)) {
859
- throw new Error("prior-good promotion evidence rejected");
860
- }
861
- atomicWrite(
862
- promotionTransactionPath(profilesRoot, profileId),
863
- `${JSON.stringify(transaction, null, 2)}\n`,
864
- 0o600,
865
- );
866
- fsyncDirectory(profilesRoot);
867
- }
868
-
869
- function removePromotionTransaction(profilesRoot, profileId) {
870
- const pathValue = promotionTransactionPath(profilesRoot, profileId);
871
- if (existsSync(pathValue)) {
872
- const link = lstatSync(pathValue);
873
- if (link.isSymbolicLink() || !link.isFile()) throw new Error("promotion transaction rejected");
874
- rmSync(pathValue);
875
- fsyncDirectory(profilesRoot);
876
- }
877
- }
878
-
879
- function readPromotionTransaction(profilesRoot, profileId, authority, currentFence) {
880
- const pathValue = promotionTransactionPath(profilesRoot, profileId);
881
- if (!existsSync(pathValue)) return null;
882
- const link = lstatSync(pathValue);
883
- if (link.isSymbolicLink() || !link.isFile() || (link.mode & 0o777) !== 0o600) {
884
- throw new Error("promotion transaction rejected");
885
- }
886
- const transaction = JSON.parse(readFileSync(pathValue, "utf8"));
887
- if (
888
- !exactKeys(transaction, [
889
- "version", "status", "profileId", "operationId", "effectId", "signerGeneration",
890
- "targetRevisionId", "targetRevisionSequence", "targetProfileDigest", "fence",
891
- "reusedSecrets", "priorObservation", "targetObservationDigest", "signature",
892
- ]) || transaction.version !== PROMOTION_TRANSACTION_VERSION ||
893
- !["PREPARED", "COMMITTED"].includes(transaction.status) ||
894
- transaction.profileId !== profileId || !SAFE_ID.test(transaction.targetRevisionId ?? "") ||
895
- transaction.operationId !== authority.operationId || transaction.effectId !== authority.effectId ||
896
- transaction.signerGeneration !== authority.journal.signerGeneration ||
897
- !Number.isSafeInteger(transaction.targetRevisionSequence) || transaction.targetRevisionSequence < 1 ||
898
- !SHA256.test(transaction.targetProfileDigest ?? "") ||
899
- !/^[1-9][0-9]{0,19}$/.test(transaction.fence ?? "") ||
900
- typeof transaction.reusedSecrets !== "boolean" ||
901
- (transaction.reusedSecrets && !validPriorObservation(transaction.priorObservation, profileId)) ||
902
- (!transaction.reusedSecrets && transaction.priorObservation !== null) ||
903
- (transaction.status === "PREPARED" && transaction.targetObservationDigest !== null) ||
904
- (transaction.status === "COMMITTED" && !SHA256.test(transaction.targetObservationDigest ?? "")) ||
905
- !PROMOTION_SIGNATURE.test(transaction.signature ?? "") ||
906
- BigInt(currentFence) < BigInt(transaction.fence) ||
907
- authority.journal.verify(promotionSignedBody(transaction), transaction.signature) !== true
908
- ) throw new Error("promotion transaction schema rejected");
909
- return transaction;
910
- }
911
-
912
- async function restartAndProvePrior({ profileRoot, profileId, priorObservation, ports, runtimeIdentity }) {
913
- try {
914
- const manifest = JSON.parse(readFileSync(join(profileRoot, "agent-profile.json"), "utf8"));
915
- if (
916
- manifest.profileId !== profileId || manifest.revisionId !== priorObservation.revisionId ||
917
- manifest.profileDigest !== priorObservation.hashes.content
918
- ) return false;
919
- await ports.gateway.start({
920
- profileId,
921
- profileRoot,
922
- identity: runtimeIdentity,
923
- argv: ["hermes", "--profile", profileId, "gateway", "run"],
924
- env: { HOME: "/profile", NODE_ENV: "production" },
925
- slackDelivery: { supervisorRunScript: "sellable-agent-runtime/run", appFd: 8, botFd: 9 },
926
- evidence: {
927
- revisionId: manifest.revisionId,
928
- profileDigest: manifest.profileDigest,
929
- xappFingerprint: priorObservation.slack.xappFingerprint,
930
- xoxbFingerprint: priorObservation.slack.xoxbFingerprint,
931
- serviceCredentialFingerprint: priorObservation.serviceCredential.fingerprint,
932
- serviceCredentialGeneration: priorObservation.serviceCredential.generation,
933
- },
934
- });
935
- const restored = await ports.gateway.observe({ profileId, profileRoot, rollback: true });
936
- const process = restored?.process;
937
- return Boolean(
938
- stableJson(priorObservationCore(restored)) === stableJson(priorObservationCore(priorObservation)) &&
939
- process?.ready && Number.isSafeInteger(process.pid) && process.pid > 1 &&
940
- /^[1-9][0-9]{0,31}$/.test(process.startTicks ?? "") &&
941
- process.serviceUser === runtimeIdentity.user && process.uid === runtimeIdentity.uid
942
- );
943
- } catch {
944
- return false;
945
- }
946
- }
947
-
948
- export async function recoverInterruptedProfilePromotion(input = {}) {
949
- const valid = validateDesired(input.desired);
950
- const profilesRoot = traversableProfilesRoot(input.profilesRoot);
951
- const runtimeSecretsRoot = privateRoot(input.runtimeSecretsRoot);
952
- if (!valid.ok || !profilesRoot || !runtimeSecretsRoot || !portsValid(input.ports)) {
953
- return { ok: false, status: "RECOVERY_INPUT_REJECTED" };
954
- }
955
- const profileId = deriveContainedProfileId(
956
- input.desired.workspaceId, input.desired.agentId, input.desired.hostId,
957
- );
958
- try {
959
- const authority = promotionJournal(input);
960
- const transaction = readPromotionTransaction(profilesRoot, profileId, authority, input.desired.fence);
961
- if (!transaction) return { ok: true, status: "NONE", profileId };
962
- const profileRoot = join(profilesRoot, profileId);
963
- const backupRoot = join(profilesRoot, `${profileId}.rollback-prior-good`);
964
- if (transaction.status === "COMMITTED") {
965
- if (!existsSync(profileRoot)) throw new Error("committed promotion target absent");
966
- const activeManifest = JSON.parse(readFileSync(join(profileRoot, "agent-profile.json"), "utf8"));
967
- if (
968
- activeManifest.profileId !== profileId ||
969
- activeManifest.revisionId !== transaction.targetRevisionId ||
970
- activeManifest.revisionSequence !== transaction.targetRevisionSequence ||
971
- activeManifest.profileDigest !== transaction.targetProfileDigest
972
- ) throw new Error("committed promotion target rejected");
973
- const committedObservation = await input.ports.gateway.observe({
974
- profileId,
975
- profileRoot,
976
- committedRecovery: true,
977
- });
978
- if (targetObservationDigest(committedObservation, transaction) !== transaction.targetObservationDigest) {
979
- throw new Error("committed promotion observation rejected");
980
- }
981
- if (existsSync(backupRoot)) {
982
- await input.ports.ownership.remove({
983
- profileId,
984
- profileRoot: backupRoot,
985
- slot: "BACKUP",
986
- revisionId: transaction.targetRevisionId,
987
- fence: transaction.fence,
988
- });
989
- }
990
- if (existsSync(backupRoot)) throw new Error("committed promotion backup cleanup unproven");
991
- removePromotionTransaction(profilesRoot, profileId);
992
- return { ok: true, status: "COMMITTED_FINALIZED", profileId };
993
- }
994
- if (existsSync(backupRoot)) {
995
- if (existsSync(profileRoot)) {
996
- const activeManifest = JSON.parse(readFileSync(join(profileRoot, "agent-profile.json"), "utf8"));
997
- if (
998
- activeManifest.profileId !== profileId ||
999
- activeManifest.revisionId !== transaction.targetRevisionId ||
1000
- activeManifest.revisionSequence !== transaction.targetRevisionSequence ||
1001
- activeManifest.profileDigest !== transaction.targetProfileDigest
1002
- ) throw new Error("promotion transaction active target rejected");
1003
- await input.ports.ownership.remove({
1004
- profileId, profileRoot, slot: "ACTIVE",
1005
- revisionId: transaction.targetRevisionId, fence: transaction.fence,
1006
- });
1007
- }
1008
- renameSync(backupRoot, profileRoot);
1009
- fsyncDirectory(profilesRoot);
1010
- }
1011
- if (!existsSync(profileRoot)) throw new Error("prior-good profile absent");
1012
- if (!transaction.reusedSecrets) {
1013
- cleanupSecrets(runtimeSecretsRoot, profileId);
1014
- await input.ports.credential.cleanup({ profileId });
1015
- removePromotionTransaction(profilesRoot, profileId);
1016
- return { ok: false, status: "RECOVERY_UNPROVEN", profileId };
1017
- }
1018
- const proven = await restartAndProvePrior({
1019
- profileRoot,
1020
- profileId,
1021
- priorObservation: transaction.priorObservation,
1022
- ports: input.ports,
1023
- runtimeIdentity: input.runtimeIdentity,
1024
- });
1025
- if (!proven) return { ok: false, status: "RECOVERY_UNPROVEN", profileId };
1026
- removePromotionTransaction(profilesRoot, profileId);
1027
- return {
1028
- ok: true,
1029
- status: "RECOVERED_AND_RESTARTED",
1030
- profileId,
1031
- priorObservation: transaction.priorObservation,
1032
- };
1033
- } catch {
1034
- return { ok: false, status: "RECOVERY_UNPROVEN", profileId };
1035
- }
1036
- }
1037
-
1038
- export function commitRetainedProfilePromotion(input = {}) {
1039
- const valid = validateDesired(input.desired);
1040
- const profilesRoot = traversableProfilesRoot(input.profilesRoot);
1041
- if (!valid.ok || !profilesRoot) return { ok: false, status: "COMMIT_INPUT_REJECTED" };
1042
- const profileId = deriveContainedProfileId(
1043
- input.desired.workspaceId, input.desired.agentId, input.desired.hostId,
1044
- );
1045
- try {
1046
- const authority = promotionJournal(input);
1047
- const transaction = readPromotionTransaction(profilesRoot, profileId, authority, input.desired.fence);
1048
- const profileRoot = join(profilesRoot, profileId);
1049
- const backupRoot = join(profilesRoot, `${profileId}.rollback-prior-good`);
1050
- if (!existsSync(profileRoot)) throw new Error("promotion commit target absent");
1051
- const activeManifest = JSON.parse(readFileSync(join(profileRoot, "agent-profile.json"), "utf8"));
1052
- if (
1053
- !transaction || transaction.targetRevisionId !== input.desired.revisionId ||
1054
- transaction.targetRevisionSequence !== input.desired.revisionSequence ||
1055
- transaction.fence !== input.desired.fence ||
1056
- transaction.targetProfileDigest !== activeManifest.profileDigest ||
1057
- activeManifest.profileId !== profileId ||
1058
- activeManifest.revisionId !== transaction.targetRevisionId ||
1059
- activeManifest.revisionSequence !== transaction.targetRevisionSequence ||
1060
- !existsSync(backupRoot)
1061
- ) throw new Error("promotion commit evidence rejected");
1062
- if (transaction.status === "PREPARED") {
1063
- const observedDigest = targetObservationDigest(input.targetObservation, transaction);
1064
- atomicWrite(
1065
- promotionTransactionPath(profilesRoot, profileId),
1066
- `${JSON.stringify(signPromotionTransaction({
1067
- ...promotionSignedBody(transaction),
1068
- status: "COMMITTED",
1069
- targetObservationDigest: observedDigest,
1070
- }, authority), null, 2)}\n`,
1071
- 0o600,
1072
- );
1073
- fsyncDirectory(profilesRoot);
1074
- } else if (transaction.status !== "COMMITTED") {
1075
- throw new Error("promotion commit state rejected");
1076
- }
1077
- return { ok: true, status: "COMMITTED_PENDING_CLEANUP", profileId };
1078
- } catch {
1079
- return { ok: false, status: "COMMIT_UNPROVEN", profileId };
1080
- }
1081
- }
1082
-
1083
- export function finalizeRetainedProfilePromotion(input = {}) {
1084
- const valid = validateDesired(input.desired);
1085
- const profilesRoot = traversableProfilesRoot(input.profilesRoot);
1086
- if (!valid.ok || !profilesRoot) return { ok: false, status: "FINALIZE_INPUT_REJECTED" };
1087
- const profileId = deriveContainedProfileId(
1088
- input.desired.workspaceId, input.desired.agentId, input.desired.hostId,
1089
- );
1090
- try {
1091
- const authority = promotionJournal(input);
1092
- const transaction = readPromotionTransaction(profilesRoot, profileId, authority, input.desired.fence);
1093
- const profileRoot = join(profilesRoot, profileId);
1094
- if (!transaction || transaction.status !== "COMMITTED" ||
1095
- transaction.targetRevisionId !== input.desired.revisionId ||
1096
- transaction.targetRevisionSequence !== input.desired.revisionSequence ||
1097
- transaction.fence !== input.desired.fence ||
1098
- existsSync(join(profilesRoot, `${profileId}.rollback-prior-good`)) ||
1099
- !existsSync(profileRoot)
1100
- ) throw new Error("promotion finalize evidence rejected");
1101
- const activeManifest = JSON.parse(readFileSync(join(profileRoot, "agent-profile.json"), "utf8"));
1102
- if (
1103
- activeManifest.profileId !== profileId ||
1104
- activeManifest.revisionId !== transaction.targetRevisionId ||
1105
- activeManifest.revisionSequence !== transaction.targetRevisionSequence ||
1106
- activeManifest.profileDigest !== transaction.targetProfileDigest
1107
- ) throw new Error("promotion finalize target rejected");
1108
- removePromotionTransaction(profilesRoot, profileId);
1109
- return { ok: true, status: "FINALIZED", profileId };
1110
- } catch {
1111
- return { ok: false, status: "FINALIZE_UNPROVEN", profileId };
1112
- }
1113
- }
1114
-
1115
- async function rollback({
1116
- profilesRoot, profileRoot, backupRoot, runtimeSecretsRoot, profileId, desired, ports,
1117
- started, preserveSecrets, priorObservation, runtimeIdentity,
1118
- }) {
1119
- if (started) {
1120
- try { await ports.gateway.stop({ profileId, reason: "validation_or_readback_failed" }); } catch {}
1121
- }
1122
- if (existsSync(profileRoot)) await ports.ownership.remove({
1123
- profileId, profileRoot, slot: "ACTIVE", revisionId: desired.revisionId, fence: desired.fence,
1124
- });
1125
- let outcome = "REMOVED";
1126
- if (existsSync(backupRoot)) {
1127
- renameSync(backupRoot, profileRoot);
1128
- outcome = "RESTORED";
1129
- }
1130
- if (!preserveSecrets) {
1131
- cleanupSecrets(runtimeSecretsRoot, profileId);
1132
- try { await ports.credential.cleanup({ profileId }); } catch {}
1133
- }
1134
- fsyncDirectory(profilesRoot);
1135
- if (outcome === "RESTORED") {
1136
- if (!preserveSecrets || !priorObservation) return "RESTORE_UNPROVEN";
1137
- return await restartAndProvePrior({
1138
- profileRoot, profileId, priorObservation, ports, runtimeIdentity,
1139
- }) ? "RESTORED_AND_RESTARTED" : "RESTORE_UNPROVEN";
1140
- }
1141
- return outcome;
1142
- }
1143
-
1144
- export async function materializeAgentProfile(input = {}) {
1145
- const valid = validateDesired(input.desired);
1146
- if (!valid.ok) return { ok: false, status: "DESIRED_REJECTED", code: valid.code };
1147
- if (!portsValid(input.ports)) return { ok: false, status: "PORT_REJECTED" };
1148
- const profilesRoot = traversableProfilesRoot(input.profilesRoot);
1149
- const runtimeSecretsRoot = privateRoot(input.runtimeSecretsRoot);
1150
- if (!profilesRoot || !runtimeSecretsRoot) return { ok: false, status: "PATH_REJECTED" };
1151
- if (
1152
- !input.runtimeIdentity ||
1153
- input.runtimeIdentity.user !== "sellable-agent-runtime" ||
1154
- !Number.isSafeInteger(input.runtimeIdentity.uid) || input.runtimeIdentity.uid < 1 ||
1155
- !Number.isSafeInteger(input.runtimeIdentity.gid) || input.runtimeIdentity.gid < 1
1156
- ) return { ok: false, status: "IDENTITY_REJECTED" };
1157
-
1158
- const desired = input.desired;
1159
- const profileId = deriveContainedProfileId(desired.workspaceId, desired.agentId, desired.hostId);
1160
- const profileRoot = join(profilesRoot, profileId);
1161
- const stagingRoot = join(profilesRoot, `${profileId}.staging-${desired.revisionId}-${desired.fence}`);
1162
- const backupRoot = join(profilesRoot, `${profileId}.rollback-prior-good`);
1163
- let startAttempted = false;
1164
- let promoted = false;
1165
- let recoveredInterruptedPromotion = false;
1166
- let priorObservation = null;
1167
- let reusedSecrets = false;
1168
- try {
1169
- const authority = promotionJournal(input);
1170
- const recovery = await recoverInterruptedProfilePromotion(input);
1171
- if (!recovery.ok) return {
1172
- ok: false,
1173
- status: recovery.status,
1174
- profileId,
1175
- recoveryPredicate: "exact_prior_good_restart_and_observation_required",
1176
- };
1177
- recoveredInterruptedPromotion = recovery.status === "RECOVERED_AND_RESTARTED";
1178
- if (existsSync(backupRoot)) {
1179
- return {
1180
- ok: false,
1181
- status: "RECOVERY_UNPROVEN",
1182
- profileId,
1183
- recoveryPredicate: "promotion_transaction_evidence_required",
1184
- };
1185
- }
1186
- await input.ports.ownership.remove({
1187
- profileId, profileRoot: stagingRoot, slot: "STAGING", revisionId: desired.revisionId, fence: desired.fence,
1188
- });
1189
- renderStagedProfile(stagingRoot, desired);
1190
- const staged = validateAgentProfileBundle({ profileRoot: stagingRoot, desired });
1191
- if (!staged.ok) {
1192
- removeDirect(profilesRoot, stagingRoot);
1193
- return { ok: false, status: "VALIDATION_REJECTED", code: staged.code };
1194
- }
1195
- const ownershipApplied = await input.ports.ownership.apply({
1196
- profileRoot: stagingRoot,
1197
- uid: input.runtimeIdentity.uid,
1198
- gid: input.runtimeIdentity.gid,
1199
- inventory: [...AGENT_PROFILE_INVENTORY],
1200
- profileId,
1201
- revisionId: desired.revisionId,
1202
- fence: desired.fence,
1203
- profileDigest: staged.hashes.profile,
1204
- });
1205
- const ownershipObserved = await input.ports.ownership.observe({ profileRoot: stagingRoot });
1206
- if (!ownershipApplied?.ok || !validateRuntimeOwnership(ownershipObserved, input.runtimeIdentity)) {
1207
- await input.ports.ownership.remove({
1208
- profileId, profileRoot: stagingRoot, slot: "STAGING", revisionId: desired.revisionId, fence: desired.fence,
1209
- });
1210
- return { ok: false, status: "OWNERSHIP_REJECTED" };
1211
- }
1212
-
1213
- const secrets = input.reuseInstalledSecrets
1214
- ? reuseInstalledRuntimeSecrets({
1215
- desired,
1216
- runtimeSecretsRoot,
1217
- mcpCredentialRoot: input.mcpCredentialRoot,
1218
- observation: input.reuseInstalledSecrets,
1219
- runtimeIdentity: input.runtimeIdentity,
1220
- })
1221
- : await materializeRuntimeSecrets({
1222
- desired,
1223
- runtimeSecretsRoot,
1224
- ports: input.ports,
1225
- serviceCredential: input.serviceCredential ?? input.ports.serviceCredential,
1226
- runtimeIdentity: input.runtimeIdentity,
1227
- });
1228
- if (!secrets.ok) {
1229
- await input.ports.ownership.remove({
1230
- profileId, profileRoot: stagingRoot, slot: "STAGING", revisionId: desired.revisionId, fence: desired.fence,
1231
- });
1232
- if (!secrets.reused) cleanupSecrets(runtimeSecretsRoot, profileId);
1233
- return { ok: false, status: "SECRET_BINDING_REJECTED", code: secrets.code };
1234
- }
1235
- reusedSecrets = secrets.reused === true;
1236
- if (input.prepareOnly !== true) {
1237
- const containment = await input.ports.containment.probe({
1238
- profileRoot: stagingRoot,
1239
- runtimeIdentity: input.runtimeIdentity,
1240
- workerIdentity: { workerId: desired.workerId },
1241
- serviceSecretFiles: secrets.files,
1242
- });
1243
- if (
1244
- !containment?.ok ||
1245
- containment.runtimeUid !== input.runtimeIdentity.uid ||
1246
- [
1247
- "siblingReadDenied", "workerReadDenied", "adminReadDenied", "providerBytesDenied",
1248
- "hostMountDenied", "dockerDenied", "unexpectedToolDenied", "wrongWorkspaceDispatchDenied",
1249
- ].some((key) => containment[key] !== true)
1250
- ) {
1251
- await input.ports.ownership.remove({
1252
- profileId, profileRoot: stagingRoot, slot: "STAGING", revisionId: desired.revisionId, fence: desired.fence,
1253
- });
1254
- if (!secrets.reused) {
1255
- cleanupSecrets(runtimeSecretsRoot, profileId);
1256
- await input.ports.credential.cleanup({ profileId });
1257
- }
1258
- return { ok: false, status: "CONTAINMENT_REJECTED" };
1259
- }
1260
- }
1261
-
1262
- const hadPrior = existsSync(profileRoot);
1263
- if (hadPrior && (input.reuseInstalledSecrets || input.priorKnownGoodObservation)) {
1264
- priorObservation = input.priorKnownGoodObservation ??
1265
- await input.ports.gateway.observe({ profileId, profileRoot, priorGood: true });
1266
- }
1267
- if (hadPrior) writePromotionTransaction({
1268
- profilesRoot,
1269
- profileId,
1270
- desired,
1271
- targetProfileDigest: staged.hashes.profile,
1272
- reusedSecrets,
1273
- priorObservation,
1274
- authority,
1275
- });
1276
- if (hadPrior) renameSync(profileRoot, backupRoot);
1277
- if (hadPrior && input.checkpoint) await input.checkpoint("after-backup");
1278
- renameSync(stagingRoot, profileRoot);
1279
- fsyncDirectory(profilesRoot);
1280
- promoted = true;
1281
- if (input.checkpoint) await input.checkpoint("after-promote");
1282
- const active = staged;
1283
- if (input.prepareOnly === true) {
1284
- if (!hadPrior || !reusedSecrets || !existsSync(backupRoot)) {
1285
- throw new Error("prepared promotion requires prior-good bytes and reused secrets");
1286
- }
1287
- return {
1288
- ok: true,
1289
- status: "PREPARED",
1290
- profileId,
1291
- recoveredInterruptedPromotion,
1292
- backupRetained: true,
1293
- receipt: {
1294
- producer: "sellable-agent-profile-materializer",
1295
- subject: `agent-profile:${profileId}`,
1296
- revisionId: desired.revisionId,
1297
- profileDigest: active.hashes.profile,
1298
- xappFingerprint: secrets.xappFingerprint,
1299
- xoxbFingerprint: secrets.xoxbFingerprint,
1300
- serviceCredentialFingerprint: secrets.serviceCredentialFingerprint,
1301
- serviceCredentialGeneration: secrets.serviceCredentialGeneration,
1302
- },
1303
- };
1304
- }
1305
- const expected = expectedStaticObservation({
1306
- desired,
1307
- profileId,
1308
- validation: active,
1309
- secrets,
1310
- });
1311
- startAttempted = true;
1312
- await input.ports.gateway.start({
1313
- profileId,
1314
- profileRoot,
1315
- identity: input.runtimeIdentity,
1316
- argv: ["hermes", "--profile", profileId, "gateway", "run"],
1317
- env: { HOME: "/profile", NODE_ENV: "production" },
1318
- slackDelivery: { supervisorRunScript: "sellable-agent-runtime/run", appFd: 8, botFd: 9 },
1319
- evidence: {
1320
- revisionId: desired.revisionId,
1321
- profileDigest: active.hashes.profile,
1322
- xappFingerprint: secrets.xappFingerprint,
1323
- xoxbFingerprint: secrets.xoxbFingerprint,
1324
- serviceCredentialFingerprint: secrets.serviceCredentialFingerprint,
1325
- serviceCredentialGeneration: secrets.serviceCredentialGeneration,
1326
- },
1327
- });
1328
- const observed = await input.ports.gateway.observe({ profileId, profileRoot });
1329
- if (!validateIndependentObservation(observed, expected, input.runtimeIdentity)) {
1330
- throw new Error("gateway readback mismatch");
1331
- }
1332
- const tools = await input.ports.mcp.discoverTools({ profileId, profileRoot });
1333
- if (stableJson(tools) !== stableJson(desired.toolInclude)) throw new Error("tool inventory mismatch");
1334
- const safeRead = await input.ports.mcp.safeRead({
1335
- profileId,
1336
- workspaceId: desired.workspaceId,
1337
- tool: desired.toolInclude[0],
1338
- });
1339
- if (!safeRead?.ok || safeRead.workspaceId !== desired.workspaceId) throw new Error("safe read mismatch");
1340
- const backupRetained = input.retainPriorGood === true && existsSync(backupRoot);
1341
- if (hadPrior) {
1342
- const committed = commitRetainedProfilePromotion({ ...input, targetObservation: observed });
1343
- if (!committed.ok) throw new Error("profile promotion commit unproven");
1344
- }
1345
- if (existsSync(backupRoot) && !backupRetained) await input.ports.ownership.remove({
1346
- profileId, profileRoot: backupRoot, slot: "BACKUP", revisionId: desired.revisionId, fence: desired.fence,
1347
- });
1348
- if (hadPrior && !backupRetained) {
1349
- const finalized = finalizeRetainedProfilePromotion(input);
1350
- if (!finalized.ok) throw new Error("profile promotion finalize unproven");
1351
- }
1352
- return {
1353
- ok: true,
1354
- status: "READY",
1355
- profileId,
1356
- recoveredInterruptedPromotion,
1357
- backupRetained,
1358
- observation: {
1359
- ...observed,
1360
- revisionSequence: active.manifest.revisionSequence,
1361
- tools: [...tools],
1362
- },
1363
- receipt: {
1364
- producer: "sellable-agent-profile-materializer",
1365
- subject: `agent-profile:${profileId}`,
1366
- revisionId: desired.revisionId,
1367
- profileDigest: active.hashes.profile,
1368
- xappFingerprint: secrets.xappFingerprint,
1369
- xoxbFingerprint: secrets.xoxbFingerprint,
1370
- serviceCredentialFingerprint: secrets.serviceCredentialFingerprint,
1371
- serviceCredentialGeneration: secrets.serviceCredentialGeneration,
1372
- },
1373
- };
1374
- } catch (error) {
1375
- if (error?.code === "SIMULATED_CRASH") {
1376
- return {
1377
- ok: false,
1378
- status: "CRASHED",
1379
- profileId,
1380
- recoveryPredicate: "backup_exists_and_active_profile_absent",
1381
- };
1382
- }
1383
- const outcome = await rollback({
1384
- profilesRoot,
1385
- profileRoot,
1386
- backupRoot,
1387
- runtimeSecretsRoot,
1388
- profileId,
1389
- desired,
1390
- ports: input.ports,
1391
- started: startAttempted,
1392
- preserveSecrets: reusedSecrets,
1393
- priorObservation,
1394
- runtimeIdentity: input.runtimeIdentity,
1395
- });
1396
- removePromotionTransaction(profilesRoot, profileId);
1397
- try {
1398
- if (existsSync(stagingRoot)) await input.ports.ownership.remove({
1399
- profileId, profileRoot: stagingRoot, slot: "STAGING", revisionId: desired.revisionId, fence: desired.fence,
1400
- });
1401
- } catch {}
1402
- return {
1403
- ok: false,
1404
- status: outcome === "RESTORED_AND_RESTARTED" ? "ROLLED_BACK" :
1405
- outcome === "RESTORE_UNPROVEN" ? "ROLLBACK_UNPROVEN" : "READBACK_REJECTED",
1406
- rolledBack: promoted,
1407
- rollback: { outcome },
1408
- };
1409
- }
1410
- }