@sellable/install 0.1.359 → 0.1.360-phase111.20260720061815

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 +23 -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,992 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { createHash } from "node:crypto";
3
- import {
4
- closeSync,
5
- existsSync,
6
- fsyncSync,
7
- lstatSync,
8
- mkdirSync,
9
- openSync,
10
- readFileSync,
11
- realpathSync,
12
- readdirSync,
13
- renameSync,
14
- rmSync,
15
- writeFileSync,
16
- } from "node:fs";
17
- import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
18
- import { fileURLToPath } from "node:url";
19
- import { deriveContainedProfileId } from "./profile-materializer.mjs";
20
-
21
- export const PROVISIONING_ACTION = "PROVISION_HERMES_PROFILE";
22
- export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.359";
23
- export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.620";
24
-
25
- export function deriveAgentProfileId(workspaceId, agentId, hostId) {
26
- return deriveContainedProfileId(workspaceId, agentId, hostId);
27
- }
28
-
29
- const INSTALLER_BIN = fileURLToPath(
30
- new URL("../../bin/sellable-install.mjs", import.meta.url)
31
- );
32
- const INSTALLER_PACKAGE_JSON = fileURLToPath(
33
- new URL("../../package.json", import.meta.url)
34
- );
35
- const REQUEST_KEYS = new Set([
36
- "action",
37
- "workspaceId",
38
- "agentId",
39
- "hostId",
40
- "generation",
41
- "desiredRevision",
42
- "idempotencyKey",
43
- "slackHomeChannel",
44
- ]);
45
- const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
46
- const PRIVATE_REFERENCE = /^secret:\/\/[A-Za-z0-9/_-]+$/;
47
-
48
- function validateRequest(request) {
49
- if (!request || typeof request !== "object" || Array.isArray(request)) {
50
- return { ok: false, code: "invalid_request" };
51
- }
52
- const unknown = Object.keys(request).find((key) => !REQUEST_KEYS.has(key));
53
- if (unknown) return { ok: false, code: "unknown_field", field: unknown };
54
- if (request.action !== PROVISIONING_ACTION) {
55
- return { ok: false, code: "unknown_action" };
56
- }
57
- for (const key of [
58
- "workspaceId",
59
- "agentId",
60
- "hostId",
61
- "desiredRevision",
62
- "idempotencyKey",
63
- ]) {
64
- if (typeof request[key] !== "string" || !SAFE_ID.test(request[key])) {
65
- return { ok: false, code: "invalid_field", field: key };
66
- }
67
- }
68
- if (!Number.isSafeInteger(request.generation) || request.generation < 1) {
69
- return { ok: false, code: "invalid_field", field: "generation" };
70
- }
71
- if (!/^C[A-Z0-9]{8,20}$/.test(request.slackHomeChannel)) {
72
- return { ok: false, code: "invalid_field", field: "slackHomeChannel" };
73
- }
74
- return { ok: true };
75
- }
76
-
77
- function isInside(root, target) {
78
- const rel = relative(root, target);
79
- return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
80
- }
81
-
82
- function existingRealDirectory(pathValue) {
83
- if (!isAbsolute(pathValue) || !existsSync(pathValue)) return null;
84
- const link = lstatSync(pathValue);
85
- if (link.isSymbolicLink() || !link.isDirectory()) return null;
86
- return realpathSync(pathValue);
87
- }
88
-
89
- function assertDirectConfinedPath(profilesRoot, target, expectedBaseName) {
90
- const configuredRoot = resolve(profilesRoot);
91
- const realRoot = existingRealDirectory(configuredRoot);
92
- if (!realRoot) return false;
93
- const resolved = resolve(target);
94
- if (
95
- dirname(resolved) !== configuredRoot ||
96
- basename(resolved) !== expectedBaseName ||
97
- !isInside(configuredRoot, resolved)
98
- ) {
99
- return false;
100
- }
101
- if (existsSync(resolved)) {
102
- const link = lstatSync(resolved);
103
- if (link.isSymbolicLink()) return false;
104
- const real = realpathSync(resolved);
105
- if (!isInside(realRoot, real) || dirname(real) !== realRoot) {
106
- return false;
107
- }
108
- }
109
- return true;
110
- }
111
-
112
- function resolveServerPaths(request, deps) {
113
- try {
114
- const profileId = deriveAgentProfileId(
115
- request.workspaceId,
116
- request.agentId,
117
- request.hostId
118
- );
119
- const configuredProfilesRoot =
120
- typeof deps?.profilesRoot === "string" ? resolve(deps.profilesRoot) : null;
121
- const profilesRoot = existingRealDirectory(configuredProfilesRoot);
122
- const runtimeHome = existingRealDirectory(deps?.runtimeHome);
123
- if (!profilesRoot || !runtimeHome) {
124
- return { ok: false, code: "invalid_server_configuration" };
125
- }
126
- const configuredProfile = deps?.profileRoots?.[profileId];
127
- if (
128
- typeof configuredProfile !== "string" ||
129
- resolve(configuredProfile) !==
130
- join(configuredProfilesRoot, profileId) ||
131
- !assertDirectConfinedPath(
132
- configuredProfilesRoot,
133
- configuredProfile,
134
- profileId
135
- )
136
- ) {
137
- return { ok: false, code: "path_confinement_failed" };
138
- }
139
- const profileRoot = resolve(configuredProfile);
140
- const stagingBase = `${profileId}.staging-${request.idempotencyKey}`;
141
- const backupBase = `${profileId}.rollback-${request.idempotencyKey}`;
142
- const stagingRoot = join(configuredProfilesRoot, stagingBase);
143
- const backupRoot = join(configuredProfilesRoot, backupBase);
144
- if (
145
- !assertDirectConfinedPath(configuredProfilesRoot, stagingRoot, stagingBase) ||
146
- !assertDirectConfinedPath(configuredProfilesRoot, backupRoot, backupBase)
147
- ) {
148
- return { ok: false, code: "path_confinement_failed" };
149
- }
150
-
151
- const credentialRoot = existingRealDirectory(deps?.credentialRoot);
152
- const credentialFile = deps?.credentialFiles?.[profileId];
153
- if (
154
- !credentialRoot ||
155
- typeof credentialFile !== "string" ||
156
- !isAbsolute(credentialFile) ||
157
- !existsSync(credentialFile)
158
- ) {
159
- return { ok: false, code: "credential_confinement_failed" };
160
- }
161
- const credentialLink = lstatSync(credentialFile);
162
- const credentialReal = credentialLink.isSymbolicLink()
163
- ? null
164
- : realpathSync(credentialFile);
165
- if (
166
- !credentialReal ||
167
- !credentialLink.isFile() ||
168
- !isInside(credentialRoot, credentialReal) ||
169
- (credentialLink.mode & 0o077) !== 0
170
- ) {
171
- return { ok: false, code: "credential_confinement_failed" };
172
- }
173
- const serviceCredentialReference =
174
- deps?.serviceCredentialReferences?.[profileId];
175
- if (!PRIVATE_REFERENCE.test(serviceCredentialReference ?? "")) {
176
- return { ok: false, code: "invalid_server_configuration" };
177
- }
178
- const credentialKeyVersion = deps?.credentialKeyVersions?.[profileId];
179
- if (typeof credentialKeyVersion !== "string" || !SAFE_ID.test(credentialKeyVersion)) {
180
- return { ok: false, code: "invalid_server_configuration" };
181
- }
182
- if (
183
- !Number.isSafeInteger(deps.runtimeUid) ||
184
- deps.runtimeUid < 1 ||
185
- !Number.isSafeInteger(deps.runtimeGid) ||
186
- deps.runtimeGid < 1
187
- ) {
188
- return { ok: false, code: "invalid_server_configuration" };
189
- }
190
- return {
191
- ok: true,
192
- profileId,
193
- profilesRoot: configuredProfilesRoot,
194
- profileRoot,
195
- stagingRoot,
196
- backupRoot,
197
- credentialFile: resolve(credentialFile),
198
- serviceCredentialReference,
199
- credentialKeyVersion,
200
- runtimeHome,
201
- };
202
- } catch {
203
- return { ok: false, code: "path_confinement_failed" };
204
- }
205
- }
206
-
207
- export function detectPinnedInstallerCapabilities() {
208
- try {
209
- const packageJson = JSON.parse(readFileSync(INSTALLER_PACKAGE_JSON, "utf8"));
210
- const source = readFileSync(INSTALLER_BIN, "utf8");
211
- const installVersion = packageJson.version;
212
- const capabilities = {
213
- hermesProfileBootstrap:
214
- source.includes('rawArgs[0] === "hermes"') &&
215
- source.includes('rawArgs[2] === "bootstrap"'),
216
- tokenFile: source.includes('arg === "--token-file"'),
217
- profileRoot: source.includes('arg === "--profile-root"'),
218
- workspaceLock: source.includes('arg === "--require-workspace-lock"'),
219
- jsonReceipt: source.includes('arg === "--json"'),
220
- };
221
- if (
222
- `${packageJson.name}@${installVersion}` !== PINNED_INSTALL_PACKAGE ||
223
- Object.values(capabilities).some((value) => value !== true)
224
- ) {
225
- return { ok: false, code: "installer_capability_mismatch" };
226
- }
227
- return {
228
- ok: true,
229
- installPackage: PINNED_INSTALL_PACKAGE,
230
- installVersion,
231
- mcpPackage: PINNED_MCP_PACKAGE,
232
- capabilities,
233
- };
234
- } catch {
235
- return { ok: false, code: "installer_capability_mismatch" };
236
- }
237
- }
238
-
239
- function hasExactKeys(value, keys) {
240
- return (
241
- value &&
242
- typeof value === "object" &&
243
- !Array.isArray(value) &&
244
- Object.keys(value).sort().join("\0") === [...keys].sort().join("\0")
245
- );
246
- }
247
-
248
- function isStringArray(value) {
249
- return Array.isArray(value) && value.every((item) => typeof item === "string");
250
- }
251
-
252
- export function validateInstallerAttestation({ stdout, expected }) {
253
- const failed = { ok: false, code: "installer_attestation_failed" };
254
- try {
255
- const parsed = JSON.parse(stdout);
256
- const stagingRoot = resolve(expected.stagingRoot);
257
- const expectedPaths = {
258
- profileRoot: stagingRoot,
259
- hermesConfigPath: join(stagingRoot, "config.yaml"),
260
- envPath: join(stagingRoot, ".env"),
261
- sellableConfigPath: join(stagingRoot, "sellable", "config.json"),
262
- sellableConfigsDir: join(stagingRoot, "sellable", "configs"),
263
- skillsRoot: join(stagingRoot, "skills", "sellable"),
264
- };
265
- if (
266
- !hasExactKeys(parsed, [
267
- "ok",
268
- "command",
269
- "profile",
270
- "dryRun",
271
- "paths",
272
- "auth",
273
- "slack",
274
- "created",
275
- "updated",
276
- "preserved",
277
- "skipped",
278
- "warnings",
279
- ]) ||
280
- parsed.ok !== true ||
281
- parsed.command !== "hermes profile bootstrap" ||
282
- parsed.profile !== expected.profileId ||
283
- parsed.dryRun !== false ||
284
- !hasExactKeys(parsed.paths, Object.keys(expectedPaths)) ||
285
- Object.entries(expectedPaths).some(([key, value]) => parsed.paths[key] !== value) ||
286
- !hasExactKeys(parsed.auth, [
287
- "apiUrl",
288
- "activeWorkspaceId",
289
- "activeWorkspaceName",
290
- "workspaceLock",
291
- "tokenPresent",
292
- "tokenSource",
293
- "tokenFingerprint",
294
- ]) ||
295
- parsed.auth.apiUrl !== "https://app.sellable.dev" ||
296
- parsed.auth.activeWorkspaceId !== expected.workspaceId ||
297
- ![null, undefined].includes(parsed.auth.activeWorkspaceName) ||
298
- !hasExactKeys(parsed.auth.workspaceLock, ["enabled", "workspaceId", "required"]) ||
299
- parsed.auth.workspaceLock.enabled !== true ||
300
- parsed.auth.workspaceLock.workspaceId !== expected.workspaceId ||
301
- parsed.auth.workspaceLock.required !== true ||
302
- parsed.auth.tokenPresent !== true ||
303
- parsed.auth.tokenSource !== "token-file" ||
304
- !/^[a-f0-9]{16}$/.test(parsed.auth.tokenFingerprint) ||
305
- !hasExactKeys(parsed.slack, ["keys", "envKeys", "fingerprints", "scope"]) ||
306
- !Array.isArray(parsed.slack.keys) ||
307
- parsed.slack.keys.length !== 0 ||
308
- JSON.stringify(parsed.slack.envKeys) !==
309
- JSON.stringify(["SLACK_HOME_CHANNEL", "SLACK_ALLOWED_CHANNELS"]) ||
310
- !hasExactKeys(parsed.slack.fingerprints, []) ||
311
- !hasExactKeys(parsed.slack.scope, [
312
- "homeChannel",
313
- "homeChannelName",
314
- "allowedChannels",
315
- "freeResponseChannels",
316
- "allowedUsersCount",
317
- "requireMention",
318
- ]) ||
319
- parsed.slack.scope.homeChannel !== expected.slackHomeChannel ||
320
- parsed.slack.scope.homeChannelName !== null ||
321
- JSON.stringify(parsed.slack.scope.allowedChannels) !==
322
- JSON.stringify([expected.slackHomeChannel]) ||
323
- !Array.isArray(parsed.slack.scope.freeResponseChannels) ||
324
- parsed.slack.scope.freeResponseChannels.length !== 0 ||
325
- parsed.slack.scope.allowedUsersCount !== 0 ||
326
- parsed.slack.scope.requireMention !== null ||
327
- !isStringArray(parsed.created) ||
328
- !isStringArray(parsed.updated) ||
329
- !isStringArray(parsed.preserved) ||
330
- !isStringArray(parsed.skipped) ||
331
- !isStringArray(parsed.warnings)
332
- ) {
333
- return failed;
334
- }
335
- const allowedMaterializedPaths = new Set(Object.values(expectedPaths).slice(1));
336
- if (
337
- [...parsed.created, ...parsed.updated, ...parsed.preserved].some(
338
- (value) => !allowedMaterializedPaths.has(value)
339
- ) ||
340
- /xox[bap]-|sat_[^\s"']+|authorization[_-]?code|access[_-]?token|refresh[_-]?token/i.test(
341
- stdout
342
- )
343
- ) {
344
- return failed;
345
- }
346
- return {
347
- ok: true,
348
- attestation: {
349
- credentialFingerprint: parsed.auth.tokenFingerprint,
350
- tokenSource: "token-file",
351
- },
352
- };
353
- } catch {
354
- return failed;
355
- }
356
- }
357
-
358
- export function buildProvisioningInvocation(request, deps) {
359
- const valid = validateRequest(request);
360
- if (!valid.ok) return valid;
361
- const paths = resolveServerPaths(request, deps);
362
- if (!paths.ok) return paths;
363
- return {
364
- ok: true,
365
- invocation: {
366
- package: PINNED_INSTALL_PACKAGE,
367
- mcpPackage: PINNED_MCP_PACKAGE,
368
- command: process.execPath,
369
- args: [
370
- INSTALLER_BIN,
371
- "hermes",
372
- "profile",
373
- "bootstrap",
374
- "--profile-root",
375
- paths.stagingRoot,
376
- "--profile",
377
- paths.profileId,
378
- "--workspace-id",
379
- request.workspaceId,
380
- "--token-file",
381
- paths.credentialFile,
382
- "--slack-home-channel",
383
- request.slackHomeChannel,
384
- "--api-url",
385
- "https://app.sellable.dev",
386
- "--server",
387
- "package",
388
- "--mcp-package",
389
- PINNED_MCP_PACKAGE,
390
- "--require-workspace-lock",
391
- "--json",
392
- ],
393
- env: {
394
- HOME: paths.runtimeHome,
395
- NODE_ENV: "production",
396
- PATH: deps.runtimeEnv?.PATH || "/usr/bin:/bin",
397
- },
398
- shell: false,
399
- timeoutMs: 60_000,
400
- maxOutputBytes: 64 * 1024,
401
- uid: deps.runtimeUid,
402
- gid: deps.runtimeGid,
403
- profileRoot: paths.stagingRoot,
404
- },
405
- paths,
406
- };
407
- }
408
-
409
- export async function runBoundedChildProcess(invocation) {
410
- return new Promise((resolveResult) => {
411
- let stdout = Buffer.alloc(0);
412
- let stderr = Buffer.alloc(0);
413
- let outputBytes = 0;
414
- let timedOut = false;
415
- let outputLimitExceeded = false;
416
- let terminationReason = null;
417
- let settled = false;
418
- const finish = (exitCode, signal, spawnFailed = false) => {
419
- if (settled) return;
420
- settled = true;
421
- clearTimeout(timer);
422
- resolveResult({
423
- exitCode,
424
- signal,
425
- stdout: stdout.toString("utf8"),
426
- stderr: stderr.toString("utf8"),
427
- outputBytes,
428
- timedOut,
429
- outputLimitExceeded,
430
- terminated: timedOut || outputLimitExceeded,
431
- terminationReason:
432
- terminationReason ?? (spawnFailed ? "spawn_failed" : null),
433
- });
434
- };
435
- let child;
436
- try {
437
- child = spawn(invocation.command, invocation.args, {
438
- env: invocation.env,
439
- shell: false,
440
- stdio: ["ignore", "pipe", "pipe"],
441
- ...(Number.isSafeInteger(invocation.uid) ? { uid: invocation.uid } : {}),
442
- ...(Number.isSafeInteger(invocation.gid) ? { gid: invocation.gid } : {}),
443
- });
444
- } catch {
445
- resolveResult({
446
- exitCode: null,
447
- signal: null,
448
- stdout: "",
449
- stderr: "",
450
- outputBytes: 0,
451
- timedOut: false,
452
- outputLimitExceeded: false,
453
- terminated: false,
454
- terminationReason: "spawn_failed",
455
- });
456
- return;
457
- }
458
- const capture = (target, chunk) => {
459
- const remaining = Math.max(0, invocation.maxOutputBytes - outputBytes);
460
- const kept = chunk.subarray(0, remaining);
461
- if (target === "stdout") stdout = Buffer.concat([stdout, kept]);
462
- else stderr = Buffer.concat([stderr, kept]);
463
- outputBytes += kept.length;
464
- if (chunk.length > remaining && !outputLimitExceeded) {
465
- outputLimitExceeded = true;
466
- terminationReason = "output_limit";
467
- child.kill("SIGKILL");
468
- }
469
- };
470
- child.stdout.on("data", (chunk) => capture("stdout", Buffer.from(chunk)));
471
- child.stderr.on("data", (chunk) => capture("stderr", Buffer.from(chunk)));
472
- child.once("error", () => finish(null, null, true));
473
- child.once("close", (code, signal) => finish(code, signal));
474
- const timer = setTimeout(() => {
475
- timedOut = true;
476
- terminationReason = "timeout";
477
- child.kill("SIGKILL");
478
- }, invocation.timeoutMs);
479
- timer.unref?.();
480
- });
481
- }
482
-
483
- function matchesReadback(observed, request) {
484
- const profileId = deriveAgentProfileId(
485
- request.workspaceId,
486
- request.agentId,
487
- request.hostId
488
- );
489
- return (
490
- observed?.profileId === profileId &&
491
- observed?.workspaceId === request.workspaceId &&
492
- observed?.agentId === request.agentId &&
493
- observed?.generation === request.generation &&
494
- observed?.revision === request.desiredRevision &&
495
- observed?.idempotencyKey === request.idempotencyKey
496
- );
497
- }
498
-
499
- function sha256(value) {
500
- return createHash("sha256").update(value).digest("hex");
501
- }
502
-
503
- function stableSerialize(value) {
504
- if (Array.isArray(value)) {
505
- return `[${value.map((item) => stableSerialize(item)).join(",")}]`;
506
- }
507
- if (value && typeof value === "object") {
508
- return `{${Object.keys(value)
509
- .sort()
510
- .map((key) => `${JSON.stringify(key)}:${stableSerialize(value[key])}`)
511
- .join(",")}}`;
512
- }
513
- return JSON.stringify(value);
514
- }
515
-
516
- function hashProfileTree(root, { excludeManifest = false } = {}) {
517
- const digest = createHash("sha256");
518
- const walk = (pathValue, rel) => {
519
- const link = lstatSync(pathValue);
520
- if (link.isSymbolicLink()) throw new Error("symlink in profile");
521
- const mode = (link.mode & 0o777).toString(8).padStart(3, "0");
522
- if (link.isDirectory()) {
523
- digest.update(`D\0${rel}\0${mode}\n`);
524
- for (const name of readdirSync(pathValue).sort()) {
525
- const childRel = rel ? `${rel}/${name}` : name;
526
- if (excludeManifest && childRel === "agent-profile.json") continue;
527
- walk(join(pathValue, name), childRel);
528
- }
529
- return;
530
- }
531
- if (!link.isFile()) throw new Error("unsupported profile entry");
532
- digest.update(`F\0${rel}\0${mode}\0${link.size}\n`);
533
- digest.update(readFileSync(pathValue));
534
- digest.update("\n");
535
- };
536
- walk(root, "");
537
- return digest.digest("hex");
538
- }
539
-
540
- function inspectProvisionedProfile({
541
- profileRoot,
542
- request,
543
- serviceCredentialReference,
544
- credentialKeyVersion,
545
- }) {
546
- try {
547
- const manifestPath = join(profileRoot, "agent-profile.json");
548
- const manifestLink = lstatSync(manifestPath);
549
- if (
550
- manifestLink.isSymbolicLink() ||
551
- !manifestLink.isFile() ||
552
- (manifestLink.mode & 0o777) !== 0o600
553
- ) {
554
- return { ok: false, code: "manifest_mode_invalid" };
555
- }
556
- const manifestBytes = readFileSync(manifestPath);
557
- const manifest = JSON.parse(manifestBytes.toString("utf8"));
558
- if (
559
- !hasExactKeys(manifest, [
560
- "profileId",
561
- "workspaceId",
562
- "agentId",
563
- "generation",
564
- "revision",
565
- "idempotencyKey",
566
- "serviceCredentialReference",
567
- "installerPackage",
568
- "installerVersion",
569
- "mcpPackage",
570
- "credentialFingerprint",
571
- "credentialKeyVersion",
572
- "profileContentDigest",
573
- "producer",
574
- "subject",
575
- ]) ||
576
- !matchesReadback(manifest, request) ||
577
- manifest.serviceCredentialReference !== serviceCredentialReference ||
578
- manifest.installerPackage !== PINNED_INSTALL_PACKAGE ||
579
- manifest.installerVersion !== "0.1.359" ||
580
- manifest.mcpPackage !== PINNED_MCP_PACKAGE ||
581
- manifest.credentialKeyVersion !== credentialKeyVersion ||
582
- !/^[a-f0-9]{16}$/.test(manifest.credentialFingerprint) ||
583
- !/^[a-f0-9]{64}$/.test(manifest.profileContentDigest) ||
584
- manifest.producer !== "sellable-agent-provisioning-adapter" ||
585
- manifest.subject !== `agent-profile:${manifest.profileId}`
586
- ) {
587
- return { ok: false, code: "manifest_binding_invalid" };
588
- }
589
- const authPath = join(profileRoot, "sellable", "config.json");
590
- const authLink = lstatSync(authPath);
591
- if (
592
- authLink.isSymbolicLink() ||
593
- !authLink.isFile() ||
594
- (authLink.mode & 0o777) !== 0o600
595
- ) {
596
- return { ok: false, code: "auth_mode_invalid" };
597
- }
598
- const envPath = join(profileRoot, ".env");
599
- if (existsSync(envPath)) {
600
- const envLink = lstatSync(envPath);
601
- if (
602
- envLink.isSymbolicLink() ||
603
- !envLink.isFile() ||
604
- (envLink.mode & 0o777) !== 0o600
605
- ) {
606
- return { ok: false, code: "env_mode_invalid" };
607
- }
608
- }
609
- const auth = JSON.parse(readFileSync(authPath, "utf8"));
610
- const fingerprint =
611
- typeof auth.token === "string" ? sha256(auth.token).slice(0, 16) : null;
612
- if (
613
- typeof auth.token !== "string" ||
614
- !auth.token.startsWith("sat_") ||
615
- auth.activeWorkspaceId !== request.workspaceId ||
616
- auth.workspaceId !== request.workspaceId ||
617
- auth.bootstrap?.profile !== manifest.profileId ||
618
- fingerprint !== manifest.credentialFingerprint
619
- ) {
620
- return { ok: false, code: "auth_binding_invalid" };
621
- }
622
- const contentDigest = hashProfileTree(profileRoot, { excludeManifest: true });
623
- if (contentDigest !== manifest.profileContentDigest) {
624
- return { ok: false, code: "profile_content_digest_mismatch" };
625
- }
626
- return {
627
- ok: true,
628
- observed: manifest,
629
- credential: { fingerprint, keyVersion: credentialKeyVersion },
630
- digests: {
631
- current: hashProfileTree(profileRoot),
632
- manifest: sha256(manifestBytes),
633
- content: contentDigest,
634
- },
635
- };
636
- } catch {
637
- return { ok: false, code: "profile_inspection_failed" };
638
- }
639
- }
640
-
641
- function safeRemoveDirect(profilesRoot, target) {
642
- const targetBase = basename(target);
643
- if (!assertDirectConfinedPath(profilesRoot, target, targetBase)) {
644
- throw new Error("confined removal rejected");
645
- }
646
- if (existsSync(target)) rmSync(target, { recursive: true, force: true });
647
- }
648
-
649
- function fsyncPath(pathValue) {
650
- const descriptor = openSync(pathValue, "r");
651
- try {
652
- fsyncSync(descriptor);
653
- } finally {
654
- closeSync(descriptor);
655
- }
656
- }
657
-
658
- function fsyncTree(root) {
659
- const link = lstatSync(root);
660
- if (link.isSymbolicLink()) throw new Error("symlink in staged profile");
661
- if (link.isDirectory()) {
662
- for (const name of readdirSync(root)) fsyncTree(join(root, name));
663
- }
664
- fsyncPath(root);
665
- }
666
-
667
- async function rollbackPromotedProfile({ profilesRoot, profileRoot, backupRoot }) {
668
- if (existsSync(profileRoot)) safeRemoveDirect(profilesRoot, profileRoot);
669
- if (existsSync(backupRoot)) renameSync(backupRoot, profileRoot);
670
- fsyncPath(profilesRoot);
671
- }
672
-
673
- export async function atomicPromoteProfile({
674
- profilesRoot,
675
- profileRoot,
676
- stagingRoot,
677
- backupRoot,
678
- }) {
679
- const configuredProfilesRoot = resolve(profilesRoot);
680
- const realProfilesRoot = existingRealDirectory(configuredProfilesRoot);
681
- if (!realProfilesRoot) throw new Error("profile promotion confinement failed");
682
- for (const target of [profileRoot, stagingRoot, backupRoot]) {
683
- if (
684
- !assertDirectConfinedPath(
685
- configuredProfilesRoot,
686
- target,
687
- basename(target)
688
- )
689
- ) {
690
- throw new Error("profile promotion confinement failed");
691
- }
692
- }
693
- if (existsSync(stagingRoot)) fsyncTree(stagingRoot);
694
- if (existsSync(backupRoot))
695
- safeRemoveDirect(configuredProfilesRoot, backupRoot);
696
- const hadPrevious = existsSync(profileRoot);
697
- if (hadPrevious) renameSync(profileRoot, backupRoot);
698
- try {
699
- renameSync(stagingRoot, profileRoot);
700
- fsyncPath(configuredProfilesRoot);
701
- return { hadPrevious };
702
- } catch {
703
- if (existsSync(profileRoot))
704
- safeRemoveDirect(configuredProfilesRoot, profileRoot);
705
- if (hadPrevious && existsSync(backupRoot)) renameSync(backupRoot, profileRoot);
706
- fsyncPath(configuredProfilesRoot);
707
- throw new Error("profile promotion failed; prior profile restored");
708
- }
709
- }
710
-
711
- export async function promoteProfileWithReadback(input) {
712
- let promotion;
713
- try {
714
- promotion = await atomicPromoteProfile(input);
715
- } catch {
716
- return { ok: false, code: "promotion_failed" };
717
- }
718
- try {
719
- const observed = JSON.parse(
720
- readFileSync(join(input.profileRoot, "agent-profile.json"), "utf8")
721
- );
722
- if (!matchesReadback(observed, input.request)) {
723
- await rollbackPromotedProfile(input);
724
- return {
725
- ok: false,
726
- code: "readback_mismatch",
727
- rolledBack: true,
728
- rollback: { outcome: promotion.hadPrevious ? "RESTORED" : "REMOVED" },
729
- };
730
- }
731
- if (input.expectedInspection) {
732
- const inspection = inspectProvisionedProfile({
733
- profileRoot: input.profileRoot,
734
- request: input.request,
735
- serviceCredentialReference: input.expectedInspection.serviceCredentialReference,
736
- credentialKeyVersion: input.expectedInspection.credentialKeyVersion,
737
- });
738
- if (
739
- !inspection.ok ||
740
- inspection.digests.current !== input.expectedInspection.currentDigest
741
- ) {
742
- await rollbackPromotedProfile(input);
743
- return {
744
- ok: false,
745
- code: "readback_mismatch",
746
- rolledBack: true,
747
- rollback: { outcome: promotion.hadPrevious ? "RESTORED" : "REMOVED" },
748
- };
749
- }
750
- return { ok: true, observed, inspection };
751
- }
752
- return { ok: true, observed };
753
- } catch {
754
- await rollbackPromotedProfile(input);
755
- return {
756
- ok: false,
757
- code: "readback_failed",
758
- rolledBack: true,
759
- rollback: { outcome: promotion.hadPrevious ? "RESTORED" : "REMOVED" },
760
- };
761
- }
762
- }
763
-
764
- function finalizeReceipt(receipt) {
765
- return { ...receipt, receiptDigest: sha256(stableSerialize(receipt)) };
766
- }
767
-
768
- function successReceipt({
769
- request,
770
- stages,
771
- inspection,
772
- previousDigest,
773
- outputBytes,
774
- reused = false,
775
- }) {
776
- const observed = inspection.observed;
777
- return finalizeReceipt({
778
- ok: true,
779
- action: PROVISIONING_ACTION,
780
- producer: "sellable-agent-provisioning-adapter",
781
- subject: `agent-profile:${observed.profileId}`,
782
- cli: {
783
- installPackage: PINNED_INSTALL_PACKAGE,
784
- installVersion: "0.1.359",
785
- mcpPackage: PINNED_MCP_PACKAGE,
786
- command: "hermes profile bootstrap",
787
- },
788
- stages,
789
- observed: {
790
- profileId: observed.profileId,
791
- workspaceId: observed.workspaceId,
792
- agentId: observed.agentId,
793
- generation: observed.generation,
794
- revision: observed.revision,
795
- },
796
- credential: inspection.credential,
797
- digests: {
798
- previous: previousDigest,
799
- current: inspection.digests.current,
800
- readback: inspection.digests.current,
801
- manifest: inspection.digests.manifest,
802
- content: inspection.digests.content,
803
- },
804
- rollback: { outcome: "NOT_REQUIRED" },
805
- idempotencyKey: request.idempotencyKey,
806
- outputBytes,
807
- reused,
808
- });
809
- }
810
-
811
- function failure(code, stages, extra = {}, request = null) {
812
- const safeIdentity =
813
- request && SAFE_ID.test(request.workspaceId ?? "") && SAFE_ID.test(request.agentId ?? "")
814
- ? deriveAgentProfileId(request.workspaceId, request.agentId, request.hostId)
815
- : null;
816
- return finalizeReceipt({
817
- ok: false,
818
- code,
819
- action: PROVISIONING_ACTION,
820
- producer: "sellable-agent-provisioning-adapter",
821
- subject: safeIdentity ? `agent-profile:${safeIdentity}` : "agent-profile:unbound",
822
- cli: {
823
- installPackage: PINNED_INSTALL_PACKAGE,
824
- installVersion: "0.1.359",
825
- mcpPackage: PINNED_MCP_PACKAGE,
826
- command: "hermes profile bootstrap",
827
- },
828
- stages,
829
- rollback: extra.rollback ?? { outcome: "NOT_APPLICABLE" },
830
- ...(extra.terminated === true
831
- ? {
832
- terminated: true,
833
- terminationReason: extra.terminationReason,
834
- }
835
- : {}),
836
- });
837
- }
838
-
839
- export async function runProvisioningTransaction(request, deps) {
840
- const built = buildProvisioningInvocation(request, deps);
841
- if (!built.ok) return { ...built, stages: [] };
842
- const { paths, invocation } = built;
843
- const stages = [];
844
- const capability = detectPinnedInstallerCapabilities();
845
- stages.push("capability");
846
- if (!capability.ok) return failure(capability.code, stages, {}, request);
847
-
848
- let previousDigest = null;
849
- if (existsSync(paths.profileRoot)) {
850
- try {
851
- previousDigest = hashProfileTree(paths.profileRoot);
852
- } catch {
853
- previousDigest = null;
854
- }
855
- }
856
-
857
- if (existsSync(join(paths.profileRoot, "agent-profile.json"))) {
858
- const current = inspectProvisionedProfile({
859
- profileRoot: paths.profileRoot,
860
- request,
861
- serviceCredentialReference: paths.serviceCredentialReference,
862
- credentialKeyVersion: paths.credentialKeyVersion,
863
- });
864
- if (current.ok) {
865
- stages.push("readback");
866
- return successReceipt({
867
- request,
868
- stages,
869
- inspection: current,
870
- previousDigest: current.digests.current,
871
- outputBytes: 0,
872
- reused: true,
873
- });
874
- }
875
- }
876
-
877
- try {
878
- safeRemoveDirect(paths.profilesRoot, paths.stagingRoot);
879
- safeRemoveDirect(paths.profilesRoot, paths.backupRoot);
880
- stages.push("stage");
881
- const output = await runBoundedChildProcess(invocation);
882
- if (output.timedOut) {
883
- safeRemoveDirect(paths.profilesRoot, paths.stagingRoot);
884
- return failure("installer_timeout", stages, {
885
- terminated: output.terminated,
886
- terminationReason: output.terminationReason,
887
- }, request);
888
- }
889
- if (output.outputLimitExceeded) {
890
- safeRemoveDirect(paths.profilesRoot, paths.stagingRoot);
891
- return failure("installer_output_limit", stages, {
892
- terminated: output.terminated,
893
- terminationReason: output.terminationReason,
894
- }, request);
895
- }
896
- if (output.exitCode !== 0) {
897
- safeRemoveDirect(paths.profilesRoot, paths.stagingRoot);
898
- return failure("installer_failed", stages, {}, request);
899
- }
900
-
901
- stages.push("attest");
902
- const attestation = validateInstallerAttestation({
903
- stdout: output.stdout,
904
- expected: {
905
- profileId: paths.profileId,
906
- stagingRoot: paths.stagingRoot,
907
- workspaceId: request.workspaceId,
908
- slackHomeChannel: request.slackHomeChannel,
909
- },
910
- });
911
- if (!attestation.ok) {
912
- safeRemoveDirect(paths.profilesRoot, paths.stagingRoot);
913
- return failure(attestation.code, stages, {}, request);
914
- }
915
-
916
- const profileContentDigest = hashProfileTree(paths.stagingRoot, {
917
- excludeManifest: true,
918
- });
919
-
920
- const manifest = {
921
- profileId: paths.profileId,
922
- workspaceId: request.workspaceId,
923
- agentId: request.agentId,
924
- generation: request.generation,
925
- revision: request.desiredRevision,
926
- idempotencyKey: request.idempotencyKey,
927
- serviceCredentialReference: paths.serviceCredentialReference,
928
- installerPackage: PINNED_INSTALL_PACKAGE,
929
- installerVersion: capability.installVersion,
930
- mcpPackage: PINNED_MCP_PACKAGE,
931
- credentialFingerprint: attestation.attestation.credentialFingerprint,
932
- credentialKeyVersion: paths.credentialKeyVersion,
933
- profileContentDigest,
934
- producer: "sellable-agent-provisioning-adapter",
935
- subject: `agent-profile:${paths.profileId}`,
936
- };
937
- writeFileSync(
938
- join(paths.stagingRoot, "agent-profile.json"),
939
- `${JSON.stringify(manifest, null, 2)}\n`,
940
- { mode: 0o600 }
941
- );
942
- stages.push("validate");
943
- const staged = inspectProvisionedProfile({
944
- profileRoot: paths.stagingRoot,
945
- request,
946
- serviceCredentialReference: paths.serviceCredentialReference,
947
- credentialKeyVersion: paths.credentialKeyVersion,
948
- });
949
- if (!staged.ok) {
950
- safeRemoveDirect(paths.profilesRoot, paths.stagingRoot);
951
- return failure("staged_validation_failed", stages, {}, request);
952
- }
953
- fsyncTree(paths.stagingRoot);
954
- stages.push("promote");
955
- const promoted = await promoteProfileWithReadback({
956
- profilesRoot: paths.profilesRoot,
957
- profileRoot: paths.profileRoot,
958
- stagingRoot: paths.stagingRoot,
959
- backupRoot: paths.backupRoot,
960
- request,
961
- expectedInspection: {
962
- serviceCredentialReference: paths.serviceCredentialReference,
963
- credentialKeyVersion: paths.credentialKeyVersion,
964
- currentDigest: staged.digests.current,
965
- },
966
- });
967
- stages.push("readback");
968
- if (!promoted.ok) {
969
- if (promoted.rolledBack) stages.push("rollback");
970
- return failure(
971
- promoted.code,
972
- stages,
973
- { rollback: promoted.rollback ?? { outcome: "NOT_APPLICABLE" } },
974
- request
975
- );
976
- }
977
- safeRemoveDirect(paths.profilesRoot, paths.backupRoot);
978
- safeRemoveDirect(paths.profilesRoot, paths.stagingRoot);
979
- return successReceipt({
980
- request,
981
- stages,
982
- inspection: promoted.inspection,
983
- previousDigest,
984
- outputBytes: output.outputBytes,
985
- });
986
- } catch {
987
- try {
988
- safeRemoveDirect(paths.profilesRoot, paths.stagingRoot);
989
- } catch {}
990
- return failure("provisioning_failed", stages, {}, request);
991
- }
992
- }