@sellable/install 0.1.583 → 0.1.585

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.
@@ -3,12 +3,12 @@ FROM ${HERMES_IMAGE}
3
3
 
4
4
  USER root
5
5
 
6
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.582
6
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.585
7
7
  ARG MCP_PACKAGE=@sellable/mcp@0.1.860
8
8
  ARG INSTALLER_INTEGRITY
9
9
  ARG MCP_INTEGRITY=sha512-jRA1KsCV2d4Zcuo5/UmJiP8P0NaoC3ZDvUFRQOc2AMSNDVmUR6gWvtkyAiqb1lukbFWKzfVlz2/4EWzxvb7hkQ==
10
10
 
11
- RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.582" \
11
+ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.585" \
12
12
  && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.860" \
13
13
  && test -n "${INSTALLER_INTEGRITY}" \
14
14
  && test "$(npm view "${INSTALLER_PACKAGE}" dist.integrity)" = "${INSTALLER_INTEGRITY}" \
@@ -23,9 +23,9 @@ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.582" \
23
23
  && mkdir -p /opt/sellable-agent/install-root /opt/sellable-agent/mcp-root \
24
24
  && npm install --prefix /opt/sellable-agent/install-root --omit=dev --ignore-scripts --no-audit --no-fund "${INSTALLER_PACKAGE}" \
25
25
  && npm install --prefix /opt/sellable-agent/mcp-root --omit=dev --ignore-scripts --no-audit --no-fund "${MCP_PACKAGE}" \
26
- && node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.582' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.860') throw new Error('package identity rejected');" \
26
+ && node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.585' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.860') throw new Error('package identity rejected');" \
27
27
  && node --input-type=module -e "import { buildExternalRuntimeClosure } from '/opt/sellable-agent/install-root/node_modules/@sellable/install/lib/sellable-agent/external-runtime-builder.mjs'; const built = buildExternalRuntimeClosure({ mcpPackageRoot: '/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp', mcpNodeModulesRoot: '/opt/sellable-agent/mcp-root/node_modules', hermesSourceRoot: '/opt/hermes', ownerUid: 0, ownerGid: 0 }); if (!built.closureDigest) throw new Error('runtime closure rejected');" \
28
- && node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.582', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.860', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
28
+ && node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.585', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.860', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
29
29
  && npm cache clean --force
30
30
 
31
31
  COPY entrypoint.sh /usr/local/bin/sellable-agent-container-entrypoint
@@ -3,13 +3,13 @@
3
3
  This image is a dedicated Agent worker/runtime boundary beside an existing Hermes dashboard. It never replaces or reconfigures the dashboard container.
4
4
 
5
5
  Build from this directory after
6
- `@sellable/install@0.1.582` is
6
+ `@sellable/install@0.1.585` is
7
7
  published:
8
8
 
9
9
  ```sh
10
10
  docker build --platform linux/amd64 \
11
11
  --build-arg INSTALLER_INTEGRITY='sha512-…' \
12
- --tag sellable-agent-host:0.1.582 .
12
+ --tag sellable-agent-host:0.1.585 .
13
13
  ```
14
14
 
15
15
  Create a private `bootstrap/` directory owned by root with mode `0700`. It must contain `bootstrap.json`, `provider-auth`, and `slack-app`, each owned by root with mode `0600`. `bootstrap.json` contains only non-secret identities, hashes, package integrities, and the control-plane URL. The other two files contain the provider JSON and global Slack app token whose hashes are pinned by `bootstrap.json`.
@@ -15,6 +15,7 @@ ARG ADMIN_LOCKED_TREE_DIGEST
15
15
  ARG ADMIN_ARTIFACT_DIGEST
16
16
  ARG ADMIN_PACKAGE_LOCK_B64
17
17
  ARG ADMIN_PROFILE_CONTRACT_TEMPLATE_B64
18
+ ARG MCP_TRUST_ROOT_FINGERPRINT
18
19
  ARG TUS_JS_CLIENT_VERSION=4.3.1
19
20
  ARG STRIPE_CLI_PACKAGE=@stripe/cli-linux-x64@1.45.0
20
21
  ARG STRIPE_CLI_INTEGRITY=sha512-1ZhoPpoweYfynqsvhCLlTjZ7lPz5IKtxqAMT5MBy6zrwLRTP9x0T6r8h/jLYL8lRH1c7zC/6X6/XUu2V+r9Bog==
@@ -76,8 +77,22 @@ RUN set -eux; \
76
77
  /etc/cont-init.d/02-reconcile-profiles; \
77
78
  npm cache clean --force
78
79
 
80
+ # The MCP turn trust root is the same build secret the customer image bakes.
81
+ # The Admin runtime hands the Product MCP context proxy this exact file plus
82
+ # its fingerprint, so the baked bytes must match the pinned signing contract.
83
+ RUN --mount=type=secret,id=mcp_trust_root,required=true \
84
+ set -eux; \
85
+ test -s /run/secrets/mcp_trust_root; \
86
+ test -n "${MCP_TRUST_ROOT_FINGERPRINT}"; \
87
+ install -d -m 0755 /usr/local/share/sellable-agent; \
88
+ install -o root -g root -m 0444 /run/secrets/mcp_trust_root \
89
+ /usr/local/share/sellable-agent/mcp-turn-trust-root.pem; \
90
+ test "$(sha256sum /usr/local/share/sellable-agent/mcp-turn-trust-root.pem | cut -d ' ' -f 1)" = "${MCP_TRUST_ROOT_FINGERPRINT}"
91
+
79
92
  COPY fly-admin-image/admin-runtime.mjs /usr/local/lib/sellable-agent/admin-runtime/admin-runtime.mjs
80
93
  COPY fly-admin-image/start-admin-runtime /usr/local/lib/sellable-agent/admin-runtime/start-admin-runtime
94
+ COPY mcp-context-proxy.mjs /usr/local/lib/sellable-agent/mcp-context-proxy.mjs
95
+ COPY mcp-runtime-binding.mjs /usr/local/lib/sellable-agent/mcp-runtime-binding.mjs
81
96
  COPY fly-runtime-identity.mjs /usr/local/lib/sellable-agent/fly-runtime-identity.mjs
82
97
  COPY model-auth-reconciler.mjs /usr/local/lib/sellable-agent/model-auth-reconciler.mjs
83
98
  COPY model-auth-canary-reseed.mjs /usr/local/lib/sellable-agent/model-auth-canary-reseed.mjs
@@ -98,6 +113,8 @@ RUN node --input-type=module -e \
98
113
  'import { installHermesAgentBridge, HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT } from "/usr/local/lib/sellable-agent/hermes-bridge.mjs"; installHermesAgentBridge({ sourceRoot: "/opt/hermes", contract: HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT });' \
99
114
  && node --input-type=module -e \
100
115
  'await import("/usr/local/lib/sellable-agent/fly-runtime-identity.mjs");' \
116
+ && node --input-type=module -e \
117
+ 'await import("/usr/local/lib/sellable-agent/mcp-context-proxy.mjs");' \
101
118
  && install -d -m 0755 /usr/local/share/sellable-agent \
102
119
  && printf '%s\n' \
103
120
  '#!/usr/bin/env node' \
@@ -125,6 +142,8 @@ RUN node --input-type=module -e \
125
142
  && chmod 0555 \
126
143
  /usr/local/lib/sellable-agent/admin-runtime/admin-runtime.mjs \
127
144
  /usr/local/lib/sellable-agent/admin-runtime/start-admin-runtime \
145
+ /usr/local/lib/sellable-agent/mcp-context-proxy.mjs \
146
+ /usr/local/lib/sellable-agent/mcp-runtime-binding.mjs \
128
147
  /usr/local/lib/sellable-agent/fly-runtime-identity.mjs \
129
148
  /usr/local/lib/sellable-agent/model-auth-reconciler.mjs \
130
149
  /usr/local/lib/sellable-agent/model-auth-canary-reseed.mjs \
@@ -102,9 +102,19 @@ const PROFILES_ROOT = join(DATA_ROOT, "profiles");
102
102
  const RUNTIME_ROOT = join(DATA_ROOT, ".sellable-agent", "admin-runtime");
103
103
  const DELIVERY_ROOT = join(RUNTIME_ROOT, "delivery");
104
104
  const SECRET_ROOT = "/run/sellable-agent/admin-runtime";
105
+ // Turn descriptor + consumed roots for the Product MCP context proxy. No
106
+ // descriptor is ever installed on the Slack-only Admin runtime today, but the
107
+ // proxy requires both private roots to exist at startup or it fails closed.
108
+ const MCP_TURN_CONTEXT_ROOT = join(SECRET_ROOT, "mcp-turn-context");
105
109
  const HERMES = "/opt/hermes/.venv/bin/hermes";
106
110
  const HERMES_PYTHON = "/opt/hermes/.venv/bin/python";
107
111
  const NODE = "/usr/local/bin/node";
112
+ // The Product MCP runs behind the same context proxy the customer runtime
113
+ // uses, from the same stable image path. The Admin MCP stays a direct dist
114
+ // entrypoint — two distinct servers on one profile.
115
+ const MCP_CONTEXT_PROXY = "/usr/local/lib/sellable-agent/mcp-context-proxy.mjs";
116
+ const MCP_TURN_TRUST_ROOT =
117
+ "/usr/local/share/sellable-agent/mcp-turn-trust-root.pem";
108
118
  const ADMIN_INSTALL_PACKAGE_ROOT = join(
109
119
  ARTIFACT_ROOT,
110
120
  "node_modules",
@@ -561,6 +571,48 @@ function exactSoul(claim) {
561
571
  return { bytes: soul, sha256: sha256(soul), byteLength: soul.byteLength };
562
572
  }
563
573
 
574
+ // The context proxy fails closed at startup unless its descriptor roots
575
+ // exist as private directories, its trust root file matches the fingerprint
576
+ // it is handed, and its downstream command is absolute. The materializer owns
577
+ // every one of those inputs so the `sellable` server can never enter a
578
+ // fail-closed respawn loop from missing environment.
579
+ function productMcpContextProxyEnvironment(layout) {
580
+ let trustRootPem;
581
+ try {
582
+ const entry = lstatSync(MCP_TURN_TRUST_ROOT);
583
+ if (
584
+ entry.isSymbolicLink() ||
585
+ !entry.isFile() ||
586
+ (entry.mode & 0o022) !== 0
587
+ ) {
588
+ throw new Error("admin_runtime_trust_root_rejected");
589
+ }
590
+ trustRootPem = readFileSync(MCP_TURN_TRUST_ROOT, "utf8");
591
+ } catch {
592
+ throw new Error("admin_runtime_trust_root_rejected");
593
+ }
594
+ privateDirectory(MCP_TURN_CONTEXT_ROOT);
595
+ privateDirectory(join(MCP_TURN_CONTEXT_ROOT, "consumed"));
596
+ return {
597
+ SELLABLE_AGENT_TURN_DESCRIPTOR_PATH: join(
598
+ MCP_TURN_CONTEXT_ROOT,
599
+ "turn-context.json"
600
+ ),
601
+ SELLABLE_AGENT_TURN_CONSUMED_ROOT: join(MCP_TURN_CONTEXT_ROOT, "consumed"),
602
+ SELLABLE_AGENT_MCP_RUNTIME_BINDING_PATH: join(
603
+ layout.profileHome,
604
+ "sellable",
605
+ "runtime-binding.json"
606
+ ),
607
+ SELLABLE_AGENT_MCP_TRUST_ROOT_PATH: MCP_TURN_TRUST_ROOT,
608
+ SELLABLE_AGENT_MCP_TRUST_ROOT_FINGERPRINT: sha256(trustRootPem),
609
+ SELLABLE_AGENT_MCP_DOWNSTREAM_COMMAND: NODE,
610
+ SELLABLE_AGENT_MCP_DOWNSTREAM_ARGS_JSON: JSON.stringify([
611
+ `${ARTIFACT_ROOT}/node_modules/@sellable/mcp/dist/index.js`,
612
+ ]),
613
+ };
614
+ }
615
+
564
616
  function profilePackageReceipt(
565
617
  profileContract,
566
618
  alias,
@@ -569,21 +621,24 @@ function profilePackageReceipt(
569
621
  agentServiceEnv = {}
570
622
  ) {
571
623
  const entry = profileContract.packages[alias][role];
572
- const packageName = entry.name;
573
- const entrypoint =
574
- alias === "sellable"
575
- ? `${ARTIFACT_ROOT}/node_modules/@sellable/mcp/dist/index.js`
576
- : `${ARTIFACT_ROOT}/node_modules/@sellable/admin-mcp/dist/index.js`;
577
624
  return {
578
625
  ...entry,
579
626
  command: "/usr/local/bin/node",
580
- args: [entrypoint],
627
+ args:
628
+ alias === "sellable"
629
+ ? [MCP_CONTEXT_PROXY]
630
+ : [`${ARTIFACT_ROOT}/node_modules/@sellable/admin-mcp/dist/index.js`],
581
631
  // The Admin MCP resolves its packaged gateway scripts through
582
632
  // SELLABLE_ADMIN_ROOT and its CLI shell-outs through PATH, so the server
583
633
  // entry carries them instead of relying on gateway inheritance.
584
634
  ...(alias === "sellable-admin" ? { env: adminToolEnv(layout) } : {}),
585
- ...(alias === "sellable" && Object.keys(agentServiceEnv).length > 0
586
- ? { env: agentServiceEnv }
635
+ ...(alias === "sellable"
636
+ ? {
637
+ env: {
638
+ ...agentServiceEnv,
639
+ ...productMcpContextProxyEnvironment(layout),
640
+ },
641
+ }
587
642
  : {}),
588
643
  };
589
644
  }
@@ -6,7 +6,7 @@ USER root
6
6
 
7
7
  ARG MCP_PACKAGE=@sellable/mcp@0.1.860
8
8
  ARG MCP_INTEGRITY=sha512-jRA1KsCV2d4Zcuo5/UmJiP8P0NaoC3ZDvUFRQOc2AMSNDVmUR6gWvtkyAiqb1lukbFWKzfVlz2/4EWzxvb7hkQ==
9
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.582
9
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.585
10
10
  ARG TUS_JS_CLIENT_VERSION=4.3.1
11
11
  ARG HERMES_VERSION=0.20.0
12
12
  ARG TIRITH_VERSION=0.3.3
@@ -93,7 +93,7 @@ const RUNTIME_GENERATION = Number(
93
93
  const BOOT_SESSION_ID = randomUUID();
94
94
  const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.0";
95
95
  const INSTALLER_PACKAGE =
96
- process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.582";
96
+ process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.585";
97
97
  const MCP_PACKAGE =
98
98
  process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.860";
99
99
 
@@ -280,8 +280,11 @@ export function installCustomerRuntimeSecrets({
280
280
  envelopes[name].secret
281
281
  );
282
282
  }
283
- mkdirSync(join(stagingRoot, "consumed-turns"), { mode: 0o700 });
284
- chmodSync(join(stagingRoot, "consumed-turns"), 0o700);
283
+ // Must match SELLABLE_AGENT_TURN_CONSUMED_ROOT exactly: the secret-root
284
+ // swap below discards the directory customer-runtime made at boot, and
285
+ // the MCP context proxy fails closed when the nonce directory is absent.
286
+ mkdirSync(join(stagingRoot, "turn-consumed"), { mode: 0o700 });
287
+ chmodSync(join(stagingRoot, "turn-consumed"), 0o700);
285
288
  if (existsSync(activeRoot)) {
286
289
  const active = lstatSync(activeRoot);
287
290
  if (active.isSymbolicLink() || !active.isDirectory())
@@ -39,7 +39,7 @@ const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
39
39
  const VERSION = "sellable-agent-host-bootstrap/v1";
40
40
  const RECEIPT_VERSION = "sellable-agent-host-registration/v1";
41
41
  const INSTALLER_PACKAGE =
42
- "@sellable/install@0.1.582";
42
+ "@sellable/install@0.1.585";
43
43
  const MCP_PACKAGE = "@sellable/mcp@0.1.860";
44
44
  const HERMES_VERSION = "0.18.0";
45
45
  const SHA256 = /^[a-f0-9]{64}$/;
@@ -1546,7 +1546,7 @@ export function compileClaimToProfileDesired(activeClaim, config) {
1546
1546
  ]) ||
1547
1547
  pinned.hermesCli !== "hermes" ||
1548
1548
  pinned.hermesVersion !== "0.18.0" ||
1549
- pinned.installerPackage !== "@sellable/install@0.1.582" ||
1549
+ pinned.installerPackage !== "@sellable/install@0.1.585" ||
1550
1550
  pinned.mcpPackage !== "@sellable/mcp@0.1.860" ||
1551
1551
  !Array.isArray(policy.toolInclude) ||
1552
1552
  policy.toolInclude.length === 0 ||
@@ -201,7 +201,7 @@ function validateDesired(desired) {
201
201
  desired.serviceCredentialGeneration < 1 ||
202
202
  desired.hermesCli !== "hermes" ||
203
203
  desired.hermesVersion !== "0.18.0" ||
204
- desired.installerPackage !== "@sellable/install@0.1.582" ||
204
+ desired.installerPackage !== "@sellable/install@0.1.585" ||
205
205
  desired.mcpPackage !== "@sellable/mcp@0.1.860" ||
206
206
  !Array.isArray(desired.toolInclude) ||
207
207
  desired.toolInclude.length === 0 ||
@@ -25,7 +25,7 @@ import { fileURLToPath } from "node:url";
25
25
  import { deriveContainedProfileId } from "./profile-materializer.mjs";
26
26
 
27
27
  export const PROVISIONING_ACTION = "PROVISION_HERMES_PROFILE";
28
- export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.582";
28
+ export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.585";
29
29
  export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.860";
30
30
 
31
31
  export function deriveAgentProfileId(workspaceId, agentId, hostId) {
@@ -611,7 +611,7 @@ function inspectProvisionedProfile({
611
611
  !matchesReadback(manifest, request) ||
612
612
  manifest.serviceCredentialReference !== serviceCredentialReference ||
613
613
  manifest.installerPackage !== PINNED_INSTALL_PACKAGE ||
614
- manifest.installerVersion !== "0.1.582" ||
614
+ manifest.installerVersion !== "0.1.585" ||
615
615
  manifest.mcpPackage !== PINNED_MCP_PACKAGE ||
616
616
  manifest.credentialKeyVersion !== credentialKeyVersion ||
617
617
  !/^[a-f0-9]{16}$/.test(manifest.credentialFingerprint) ||
@@ -825,7 +825,7 @@ function successReceipt({
825
825
  subject: `agent-profile:${observed.profileId}`,
826
826
  cli: {
827
827
  installPackage: PINNED_INSTALL_PACKAGE,
828
- installVersion: "0.1.582",
828
+ installVersion: "0.1.585",
829
829
  mcpPackage: PINNED_MCP_PACKAGE,
830
830
  command: "hermes profile bootstrap",
831
831
  },
@@ -873,7 +873,7 @@ function failure(code, stages, extra = {}, request = null) {
873
873
  : "agent-profile:unbound",
874
874
  cli: {
875
875
  installPackage: PINNED_INSTALL_PACKAGE,
876
- installVersion: "0.1.582",
876
+ installVersion: "0.1.585",
877
877
  mcpPackage: PINNED_MCP_PACKAGE,
878
878
  command: "hermes profile bootstrap",
879
879
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.583",
3
+ "version": "0.1.585",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {