@sellable/install 0.1.601 → 0.1.602

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.601
6
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.602
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.601" \
11
+ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.602" \
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.601" \
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.601' || 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.602' || 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.601', 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.602', 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.601` is
6
+ `@sellable/install@0.1.602` 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.601 .
12
+ --tag sellable-agent-host:0.1.602 .
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`.
@@ -31,6 +31,8 @@ export const CAPABILITIES_BRIDGE_SCHEMA_VERSION =
31
31
  "sellable-agent-capabilities-bridge/v1";
32
32
  export const CAPABILITIES_PROOF_SCHEMA_VERSION =
33
33
  "sellable-agent-capabilities-proof/v1";
34
+ const CAPABILITIES_CONFIG_AUTHORITY_SCHEMA_VERSION =
35
+ "sellable-agent-capabilities-config-authority/v1";
34
36
  export const CAPABILITIES_REQUEST_MAX_BYTES = 16 * 1024;
35
37
 
36
38
  // Deliberately excludes credential-bearing, external-account, platform-admin,
@@ -192,7 +194,7 @@ function boundedText(value, max) {
192
194
  return typeof value === "string" && Buffer.byteLength(value, "utf8") <= max;
193
195
  }
194
196
 
195
- function normalizeNative(native, request, configSha256, packageSpecs) {
197
+ function normalizeNative(native, request, packageSpecs) {
196
198
  if (!native || !Array.isArray(native.tools) || !Array.isArray(native.servers))
197
199
  fail("capabilities_native_output_rejected");
198
200
  const tools = native.tools.map((row) => {
@@ -232,6 +234,7 @@ function normalizeNative(native, request, configSha256, packageSpecs) {
232
234
  fail("capabilities_tool_inventory_rejected");
233
235
 
234
236
  const aliases = expectedAliases(request.expectedRuntimeKind);
237
+ const serverConfigAuthority = new Map();
235
238
  const mcpServers = native.servers.map((row) => {
236
239
  if (
237
240
  !row ||
@@ -240,6 +243,7 @@ function normalizeNative(native, request, configSha256, packageSpecs) {
240
243
  row.transport !== "stdio" ||
241
244
  typeof row.workspaceLocked !== "boolean" ||
242
245
  typeof row.requiresWorkspaceLock !== "boolean" ||
246
+ !SHA256.test(row.configAuthoritySha256 ?? "") ||
243
247
  !["healthy", "unhealthy", "unknown"].includes(row.health) ||
244
248
  !Array.isArray(row.tools) ||
245
249
  row.tools.length > 1024 ||
@@ -281,6 +285,7 @@ function normalizeNative(native, request, configSha256, packageSpecs) {
281
285
  // authoritative inventory. ADMIN has no customer workspace lock and its
282
286
  // two direct live probes remain authoritative.
283
287
  const names = customerProductServer ? configuredNames : probedNames;
288
+ serverConfigAuthority.set(row.name, row.configAuthoritySha256);
284
289
  return Object.freeze({
285
290
  name: row.name,
286
291
  label: row.name === "sellable" ? "Sellable" : "Sellable Admin",
@@ -303,6 +308,37 @@ function normalizeNative(native, request, configSha256, packageSpecs) {
303
308
  stableJson(mcpServers.map((server) => server.name)) !== stableJson(aliases)
304
309
  )
305
310
  fail("capabilities_mcp_inventory_rejected");
311
+ // The CAS token describes effective Capabilities authority, not YAML bytes.
312
+ // Hermes' native saver may normalize formatting or materialize defaults, so
313
+ // hashing the file made an off -> on round trip semantically exact but hash-
314
+ // unstable. Raw bytes are still fenced around enumeration in readInventory.
315
+ const configSha256 = sha256(
316
+ stableJson({
317
+ schemaVersion: CAPABILITIES_CONFIG_AUTHORITY_SCHEMA_VERSION,
318
+ platform: "slack",
319
+ tools: tools.map(({ name, enabled, configured }) => ({
320
+ name,
321
+ enabled,
322
+ configured,
323
+ })),
324
+ mcpServers: mcpServers.map(
325
+ ({
326
+ name,
327
+ enabled,
328
+ transport,
329
+ workspaceLocked,
330
+ requiresWorkspaceLock,
331
+ }) => ({
332
+ name,
333
+ enabled,
334
+ transport,
335
+ workspaceLocked,
336
+ requiresWorkspaceLock,
337
+ configAuthoritySha256: serverConfigAuthority.get(name),
338
+ })
339
+ ),
340
+ })
341
+ );
306
342
  const inventory = Object.freeze({
307
343
  complete: true,
308
344
  configSha256,
@@ -316,7 +352,7 @@ function normalizeNative(native, request, configSha256, packageSpecs) {
316
352
  }
317
353
 
318
354
  const NATIVE_SCRIPT = String.raw`
319
- import json, os, stat, sys
355
+ import hashlib, json, os, stat, sys
320
356
  request = json.loads(sys.stdin.read())
321
357
  from hermes_cli.config import load_config
322
358
  from hermes_cli.mcp_config import _get_mcp_servers, _probe_single_server
@@ -403,6 +439,7 @@ def enumerate_capabilities():
403
439
  "transport": "http" if server.get("url") else "stdio" if server.get("command") else "unknown",
404
440
  "workspaceLocked": env.get("SELLABLE_LOCK_WORKSPACE_ID") == request["expectedWorkspaceId"],
405
441
  "requiresWorkspaceLock": str(env.get("SELLABLE_REQUIRE_WORKSPACE_LOCK", "")) == "1",
442
+ "configAuthoritySha256": hashlib.sha256(json.dumps(server, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")).hexdigest(),
406
443
  "health": health,
407
444
  "tools": tools,
408
445
  "configuredTools": configured_customer_tools(name, server),
@@ -505,7 +542,7 @@ function atomicProof(paths, inventory) {
505
542
 
506
543
  function readInventory(request, paths, adapter, packageSpecs) {
507
544
  const configPath = join(paths.profileRoot, "config.yaml");
508
- const configSha256 = sha256(regularFile(configPath));
545
+ const rawConfigSha256 = sha256(regularFile(configPath));
509
546
  const aliases = expectedAliases(request.expectedRuntimeKind);
510
547
  const native = adapter.enumerate({
511
548
  expectedAliases: aliases,
@@ -515,9 +552,9 @@ function readInventory(request, paths, adapter, packageSpecs) {
515
552
  ? { probeServerName: request.serverName }
516
553
  : {}),
517
554
  });
518
- if (sha256(regularFile(configPath)) !== configSha256)
555
+ if (sha256(regularFile(configPath)) !== rawConfigSha256)
519
556
  fail("capabilities_config_changed");
520
- return normalizeNative(native, request, configSha256, packageSpecs);
557
+ return normalizeNative(native, request, packageSpecs);
521
558
  }
522
559
 
523
560
  function executeLocked(request, paths, adapter, options) {
@@ -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.601
9
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.602
10
10
  ARG ADMIN_MCP_PACKAGE=@sellable/admin-mcp@0.1.186-wip.phase158.20260802034727
11
11
  ARG ADMIN_MCP_INTEGRITY=sha512-qYNew5wd2IfgVcp1UYOG9QvpF/8Mx19YJ5HQtMKzvSfUf3evePjtaKsoXLf2C/GUJ5QlLlHJo84VWOI3V01+9A==
12
12
  ARG TUS_JS_CLIENT_VERSION=4.3.1
@@ -95,7 +95,7 @@ const RUNTIME_GENERATION = Number(
95
95
  const BOOT_SESSION_ID = randomUUID();
96
96
  const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.0";
97
97
  const INSTALLER_PACKAGE =
98
- process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.601";
98
+ process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.602";
99
99
  const MCP_PACKAGE =
100
100
  process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.860";
101
101
 
@@ -38,7 +38,7 @@ import { installAgentHostServices } from "./service-installer.mjs";
38
38
  const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
39
39
  const VERSION = "sellable-agent-host-bootstrap/v1";
40
40
  const RECEIPT_VERSION = "sellable-agent-host-registration/v1";
41
- const INSTALLER_PACKAGE = "@sellable/install@0.1.601";
41
+ const INSTALLER_PACKAGE = "@sellable/install@0.1.602";
42
42
  const MCP_PACKAGE = "@sellable/mcp@0.1.860";
43
43
  const HERMES_VERSION = "0.18.0";
44
44
  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.601" ||
1549
+ pinned.installerPackage !== "@sellable/install@0.1.602" ||
1550
1550
  pinned.mcpPackage !== "@sellable/mcp@0.1.860" ||
1551
1551
  !Array.isArray(policy.toolInclude) ||
1552
1552
  policy.toolInclude.length === 0 ||
@@ -278,7 +278,7 @@ function validateDesired(desired) {
278
278
  desired.serviceCredentialGeneration < 1 ||
279
279
  desired.hermesCli !== "hermes" ||
280
280
  desired.hermesVersion !== "0.18.0" ||
281
- desired.installerPackage !== "@sellable/install@0.1.601" ||
281
+ desired.installerPackage !== "@sellable/install@0.1.602" ||
282
282
  desired.mcpPackage !== "@sellable/mcp@0.1.860" ||
283
283
  !Array.isArray(desired.toolInclude) ||
284
284
  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.601";
28
+ export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.602";
29
29
  export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.860";
30
30
  const PINNED_INSTALL_VERSION = PINNED_INSTALL_PACKAGE.slice(
31
31
  PINNED_INSTALL_PACKAGE.lastIndexOf("@") + 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.601",
3
+ "version": "0.1.602",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {