@sellable/install 0.1.687 → 0.1.689

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.687
6
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.689
7
7
  ARG MCP_PACKAGE=@sellable/mcp@0.1.901
8
8
  ARG INSTALLER_INTEGRITY
9
9
  ARG MCP_INTEGRITY=sha512-0YBT2d3YBgZFSgEcmoDCNLz/PWMiBbqMWeZoEFArWuk63/vrYATEp/qBNzYhpy7SKClLapsb1RRagdXrvo/p9g==
10
10
 
11
- RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.687" \
11
+ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.689" \
12
12
  && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.901" \
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.687" \
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.687' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.901') 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.689' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.901') 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.687', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.901', 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.689', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.901', 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,7 +3,7 @@
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.687` is
6
+ `@sellable/install@0.1.689` is
7
7
  published:
8
8
 
9
9
  ```sh
@@ -0,0 +1,53 @@
1
+ import { isAbsolute } from "node:path";
2
+ import { nativeGatewayRunning } from "./admin-native-gateway-supervisor.mjs";
3
+
4
+ const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
5
+
6
+ export function projectAdminEndpointRuntime({
7
+ activeLaunch,
8
+ activeGateway,
9
+ runtimeGeneration,
10
+ trustRootPem,
11
+ turnDescriptorPath,
12
+ }) {
13
+ const claim = activeLaunch?.claim;
14
+ const operation = claim?.operation;
15
+ const authority = claim?.runtimeAuthority;
16
+ const service = claim?.runtimeContract?.service;
17
+ const profileId = activeLaunch?.profile?.profile;
18
+ if (
19
+ !nativeGatewayRunning(activeGateway) ||
20
+ authority?.scope !== "ASSIGNED" ||
21
+ authority.runtimeArtifactId !== authority.assignedRuntimeArtifactId ||
22
+ !SAFE_ID.test(authority.runtimeId ?? "") ||
23
+ !SAFE_ID.test(operation?.workspaceId ?? "") ||
24
+ !SAFE_ID.test(operation?.agentId ?? "") ||
25
+ !SAFE_ID.test(profileId ?? "") ||
26
+ profileId !== operation?.profileId ||
27
+ !Number.isSafeInteger(runtimeGeneration) ||
28
+ runtimeGeneration < 1 ||
29
+ !Number.isSafeInteger(service?.credentialGeneration) ||
30
+ service.credentialGeneration < 1 ||
31
+ typeof trustRootPem !== "string" ||
32
+ !trustRootPem.includes("-----BEGIN PUBLIC KEY-----") ||
33
+ !trustRootPem.includes("-----END PUBLIC KEY-----") ||
34
+ !isAbsolute(turnDescriptorPath ?? "")
35
+ ) {
36
+ return null;
37
+ }
38
+ return Object.freeze({
39
+ desired: Object.freeze({
40
+ workspaceId: operation.workspaceId,
41
+ agentId: operation.agentId,
42
+ runtimeId: authority.runtimeId,
43
+ profileId,
44
+ runtimeGeneration,
45
+ serviceCredentialGeneration: service.credentialGeneration,
46
+ }),
47
+ runtimeConfig: Object.freeze({ turnDescriptorPath }),
48
+ trustRootPem,
49
+ gateway: Object.freeze({
50
+ readback: () => ({ running: nativeGatewayRunning(activeGateway) }),
51
+ }),
52
+ });
53
+ }
@@ -155,8 +155,15 @@ COPY fly-admin-image/admin-runtime.mjs /usr/local/lib/sellable-agent/admin-runti
155
155
  COPY fly-admin-image/admin-runtime-phase-telemetry.mjs /usr/local/lib/sellable-agent/admin-runtime/admin-runtime-phase-telemetry.mjs
156
156
  COPY fly-admin-image/admin-state-generation.mjs /usr/local/lib/sellable-agent/admin-runtime/admin-state-generation.mjs
157
157
  COPY fly-admin-image/start-admin-runtime /usr/local/lib/sellable-agent/admin-runtime/start-admin-runtime
158
+ COPY fly-customer-image/customer-runtime.mjs /usr/local/lib/sellable-agent/fly-customer-image/customer-runtime.mjs
159
+ COPY fly-customer-image/hermes-runs-client.mjs /usr/local/lib/sellable-agent/fly-customer-image/hermes-runs-client.mjs
160
+ COPY fly-customer-image/endpoint-uat-completion-gate.mjs /usr/local/lib/sellable-agent/fly-customer-image/endpoint-uat-completion-gate.mjs
158
161
  COPY admin-native-gateway-supervisor.mjs /usr/local/lib/sellable-agent/admin-native-gateway-supervisor.mjs
162
+ COPY admin-endpoint-runtime.mjs /usr/local/lib/sellable-agent/admin-endpoint-runtime.mjs
159
163
  COPY abandoned-runtime-scratch.mjs /usr/local/lib/sellable-agent/abandoned-runtime-scratch.mjs
164
+ COPY fly-customer-model.mjs /usr/local/lib/sellable-agent/fly-customer-model.mjs
165
+ COPY fly-customer-worker.mjs /usr/local/lib/sellable-agent/fly-customer-worker.mjs
166
+ COPY profile-materializer.mjs /usr/local/lib/sellable-agent/profile-materializer.mjs
160
167
  COPY mcp-context-proxy.mjs /usr/local/lib/sellable-agent/mcp-context-proxy.mjs
161
168
  COPY mcp-runtime-binding.mjs /usr/local/lib/sellable-agent/mcp-runtime-binding.mjs
162
169
  COPY fly-runtime-identity.mjs /usr/local/lib/sellable-agent/fly-runtime-identity.mjs
@@ -243,8 +250,15 @@ RUN set -eux; \
243
250
  /usr/local/lib/sellable-agent/admin-runtime/admin-runtime.mjs \
244
251
  /usr/local/lib/sellable-agent/admin-runtime/admin-state-generation.mjs \
245
252
  /usr/local/lib/sellable-agent/admin-runtime/start-admin-runtime \
253
+ /usr/local/lib/sellable-agent/fly-customer-image/customer-runtime.mjs \
254
+ /usr/local/lib/sellable-agent/fly-customer-image/hermes-runs-client.mjs \
255
+ /usr/local/lib/sellable-agent/fly-customer-image/endpoint-uat-completion-gate.mjs \
246
256
  /usr/local/lib/sellable-agent/admin-native-gateway-supervisor.mjs \
257
+ /usr/local/lib/sellable-agent/admin-endpoint-runtime.mjs \
247
258
  /usr/local/lib/sellable-agent/abandoned-runtime-scratch.mjs \
259
+ /usr/local/lib/sellable-agent/fly-customer-model.mjs \
260
+ /usr/local/lib/sellable-agent/fly-customer-worker.mjs \
261
+ /usr/local/lib/sellable-agent/profile-materializer.mjs \
248
262
  /usr/local/lib/sellable-agent/mcp-context-proxy.mjs \
249
263
  /usr/local/lib/sellable-agent/mcp-runtime-binding.mjs \
250
264
  /usr/local/lib/sellable-agent/fly-runtime-identity.mjs \
@@ -270,6 +284,7 @@ RUN set -eux; \
270
284
 
271
285
  LABEL io.sellable.runtime.kind="admin" \
272
286
  io.sellable.runtime.listener="native-dedicated-slack" \
287
+ io.sellable.runtime.api-server="authenticated-loopback-127.0.0.1:8642" \
273
288
  io.sellable.runtime.installers="build-only" \
274
289
  io.sellable.runtime.default-profile-bundle-version="${DEFAULT_PROFILE_BUNDLE_VERSION}" \
275
290
  io.sellable.runtime.default-profile-bundle-digest="${DEFAULT_PROFILE_BUNDLE_DIGEST}"
@@ -46,6 +46,7 @@ import {
46
46
  isOperationBoundBackupClaim,
47
47
  } from "../admin-artifact/node_modules/@sellable/install/lib/sellable-agent/fly-customer-worker.mjs";
48
48
  import sodium from "../admin-artifact/node_modules/libsodium-wrappers/dist/modules-esm/libsodium-wrappers.mjs";
49
+ import { projectAdminEndpointRuntime } from "../admin-endpoint-runtime.mjs";
49
50
  import {
50
51
  nativeGatewayRunning,
51
52
  nextAdminNativeGatewayRecovery,
@@ -60,6 +61,7 @@ import {
60
61
  SELLABLE_BROWSER_TOOLSET_DEFAULT_MARKER,
61
62
  sellableSlackToolsetsForMaterialization,
62
63
  } from "../fly-capabilities-bridge.mjs";
64
+ import { createCustomerEndpointRelay } from "../fly-customer-image/customer-runtime.mjs";
63
65
  import {
64
66
  claimWithSoulObservation,
65
67
  createFlyCustomerControlClient,
@@ -131,9 +133,9 @@ const PROFILES_ROOT = join(DATA_ROOT, "profiles");
131
133
  const RUNTIME_ROOT = join(DATA_ROOT, ".sellable-agent", "admin-runtime");
132
134
  const DELIVERY_ROOT = join(RUNTIME_ROOT, "delivery");
133
135
  const SECRET_ROOT = "/run/sellable-agent/admin-runtime";
134
- // Turn descriptor + consumed roots for the Product MCP context proxy. No
135
- // descriptor is ever installed on the Slack-only Admin runtime today, but the
136
- // proxy requires both private roots to exist at startup or it fails closed.
136
+ // Turn descriptor + consumed roots for the Product MCP context proxy. They are
137
+ // normally empty and receive one signed descriptor only while the fenced
138
+ // endpoint relay owns a turn; the proxy fails closed if either root is absent.
137
139
  const MCP_TURN_CONTEXT_ROOT = join(SECRET_ROOT, "mcp-turn-context");
138
140
  const HERMES = "/opt/hermes/.venv/bin/hermes";
139
141
  const HERMES_PYTHON = "/opt/hermes/.venv/bin/python";
@@ -693,7 +695,7 @@ function ensureProductMcpContextProxyRoots() {
693
695
  privateDirectory(join(MCP_TURN_CONTEXT_ROOT, "consumed"));
694
696
  }
695
697
 
696
- function productMcpContextProxyEnvironment(layout) {
698
+ function readMcpTurnTrustRoot() {
697
699
  let trustRootPem;
698
700
  try {
699
701
  const entry = lstatSync(MCP_TURN_TRUST_ROOT);
@@ -708,6 +710,11 @@ function productMcpContextProxyEnvironment(layout) {
708
710
  } catch {
709
711
  throw new Error("admin_runtime_trust_root_rejected");
710
712
  }
713
+ return trustRootPem;
714
+ }
715
+
716
+ function productMcpContextProxyEnvironment(layout) {
717
+ const trustRootPem = readMcpTurnTrustRoot();
711
718
  ensureProductMcpContextProxyRoots();
712
719
  return {
713
720
  SELLABLE_AGENT_TURN_DESCRIPTOR_PATH: join(
@@ -810,6 +817,10 @@ function installAdminModelConfig(profileRoot, modelConfig) {
810
817
  vision: { provider: "openrouter", model: "google/gemini-3.5-flash" },
811
818
  });
812
819
  }
820
+ document.setIn(["platforms", "api_server"], {
821
+ enabled: true,
822
+ extra: { host: "127.0.0.1", port: 8642, cors_origins: [] },
823
+ });
813
824
  atomicPrivateFile(path, document.toString());
814
825
  const readback = parseDocument(readFileSync(path, "utf8")).toJS();
815
826
  if (
@@ -820,7 +831,12 @@ function installAdminModelConfig(profileRoot, modelConfig) {
820
831
  canonicalJson(readback.credential_pool_strategies) !==
821
832
  canonicalJson(modelConfig.credential_pool_strategies) ||
822
833
  canonicalJson(readback.fallback_providers) !==
823
- canonicalJson(modelConfig.fallback_providers)
834
+ canonicalJson(modelConfig.fallback_providers) ||
835
+ canonicalJson(readback.platforms?.api_server) !==
836
+ canonicalJson({
837
+ enabled: true,
838
+ extra: { host: "127.0.0.1", port: 8642, cors_origins: [] },
839
+ })
824
840
  ) {
825
841
  throw new Error("admin_model_config_rejected");
826
842
  }
@@ -1826,6 +1842,10 @@ function installRuntimeSecrets({ claim, secrets, desiredLifecycle }) {
1826
1842
  );
1827
1843
  atomicPrivateFile(join(SECRET_ROOT, "slack-app"), `${secrets.slackApp}\n`);
1828
1844
  atomicPrivateFile(join(SECRET_ROOT, "slack-bot"), `${secrets.slackBot}\n`);
1845
+ atomicPrivateFile(
1846
+ join(SECRET_ROOT, "api-server-key"),
1847
+ sha256(`admin-endpoint-api\0${randomUUID()}\0${Date.now()}`)
1848
+ );
1829
1849
  const slackSecretFile = join(SECRET_ROOT, "profile-slack.env");
1830
1850
  atomicPrivateFile(
1831
1851
  slackSecretFile,
@@ -2032,6 +2052,7 @@ function clearRuntimeSecrets() {
2032
2052
  for (const name of [
2033
2053
  "slack-app",
2034
2054
  "slack-bot",
2055
+ "api-server-key",
2035
2056
  "profile-slack.env",
2036
2057
  "sellable-config.json",
2037
2058
  ]) {
@@ -2869,10 +2890,9 @@ async function startNativeGateway({ profile, claim }) {
2869
2890
  profileRoot: profile.profileHome,
2870
2891
  secretsRoot: SECRET_ROOT,
2871
2892
  profileId: profile.profile,
2872
- // Authenticated Hermes HTTP endpoints are CUSTOMER-only. The broker
2873
- // intentionally does not issue a hermes-api-server secret to ADMIN
2874
- // runtimes, whose native gateway remains Slack-only.
2875
- apiServerKeyRequired: false,
2893
+ // The endpoint relay is runtime-local and uses a fresh tmpfs key. The key
2894
+ // never crosses the control plane and Hermes still binds only loopback.
2895
+ apiServerKeyRequired: true,
2876
2896
  env: {
2877
2897
  ...adminToolEnv(adminHomeLayout(profile.profileHome)),
2878
2898
  ...adminAgentServiceEnvironment(claim),
@@ -2896,11 +2916,17 @@ async function startNativeGateway({ profile, claim }) {
2896
2916
  launch.inheritedFileDescriptors.find(({ fd }) => fd === 9).path,
2897
2917
  "r"
2898
2918
  );
2919
+ const apiServerKey = readFileSync(launch.apiServerKeyFile, "utf8");
2920
+ if (!/^[a-f0-9]{64}$/.test(apiServerKey)) {
2921
+ closeSync(appFd);
2922
+ closeSync(botFd);
2923
+ throw new Error("admin_endpoint_api_key_rejected");
2924
+ }
2899
2925
  let child;
2900
2926
  try {
2901
2927
  child = spawn(HERMES, launch.args, {
2902
2928
  cwd: launch.cwd,
2903
- env: launch.env,
2929
+ env: { ...launch.env, API_SERVER_KEY: apiServerKey },
2904
2930
  stdio: [
2905
2931
  "ignore",
2906
2932
  "ignore",
@@ -3566,12 +3592,28 @@ async function runAdminProduction() {
3566
3592
  let activeLaunch = null;
3567
3593
  let activeModelHealthReporter = null;
3568
3594
  let shutdownMemorySnapshot = null;
3595
+ const endpointRelay = createCustomerEndpointRelay({
3596
+ getControlClient: () => client,
3597
+ getActiveRuntime: () =>
3598
+ projectAdminEndpointRuntime({
3599
+ activeLaunch,
3600
+ activeGateway,
3601
+ runtimeGeneration: RUNTIME_GENERATION,
3602
+ trustRootPem: readMcpTurnTrustRoot(),
3603
+ turnDescriptorPath: join(MCP_TURN_CONTEXT_ROOT, "turn-context.json"),
3604
+ }),
3605
+ runtimeGeneration: RUNTIME_GENERATION,
3606
+ getApiKeyFile: () => join(SECRET_ROOT, "api-server-key"),
3607
+ diagnostic: (value) => process.stderr.write(`${JSON.stringify(value)}\n`),
3608
+ });
3609
+ const endpointLoop = endpointRelay.run(controller.signal);
3569
3610
  const beginShutdownSnapshot = () => {
3570
3611
  if (!activeLaunch || shutdownMemorySnapshot) return;
3571
3612
  const launch = activeLaunch;
3572
3613
  const gateway = activeGateway;
3573
3614
  shutdownMemorySnapshot = (async () => {
3574
3615
  try {
3616
+ await endpointRelay.fenceActive("RUNTIME_SHUTDOWN");
3575
3617
  await stopNativeGateway(gateway);
3576
3618
  if (activeGateway === gateway) activeGateway = null;
3577
3619
  return await flushHermesSnapshot({
@@ -3677,6 +3719,7 @@ async function runAdminProduction() {
3677
3719
  !nativeGatewayRunning(activeGateway) &&
3678
3720
  !gatewayRestartExhaustionReported
3679
3721
  ) {
3722
+ await endpointRelay.fenceActive("GATEWAY_RESTART");
3680
3723
  const recovery = await recoverUnexpectedNativeGateway({
3681
3724
  activeLaunch,
3682
3725
  activeGateway,
@@ -3768,6 +3811,7 @@ async function runAdminProduction() {
3768
3811
  });
3769
3812
  await activeModelHealthReporter?.stop();
3770
3813
  activeModelHealthReporter = null;
3814
+ await endpointRelay.fenceActive("LIFECYCLE_REFRESH");
3771
3815
  await stopNativeGateway(activeGateway);
3772
3816
  activeGateway = null;
3773
3817
  activeLaunch = null;
@@ -4163,6 +4207,7 @@ async function runAdminProduction() {
4163
4207
  })}\n`
4164
4208
  );
4165
4209
  }
4210
+ await endpointLoop;
4166
4211
  await activeModelHealthReporter?.stop();
4167
4212
  await stopNativeGateway(activeGateway);
4168
4213
  clearRuntimeSecrets();
@@ -8,7 +8,7 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright
8
8
 
9
9
  ARG MCP_PACKAGE=@sellable/mcp@0.1.901
10
10
  ARG MCP_INTEGRITY=sha512-0YBT2d3YBgZFSgEcmoDCNLz/PWMiBbqMWeZoEFArWuk63/vrYATEp/qBNzYhpy7SKClLapsb1RRagdXrvo/p9g==
11
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.687
11
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.689
12
12
  ARG ADMIN_MCP_PACKAGE=@sellable/admin-mcp@0.1.186-wip.phase158.20260802034727
13
13
  ARG ADMIN_MCP_INTEGRITY=sha512-qYNew5wd2IfgVcp1UYOG9QvpF/8Mx19YJ5HQtMKzvSfUf3evePjtaKsoXLf2C/GUJ5QlLlHJo84VWOI3V01+9A==
14
14
  ARG TUS_JS_CLIENT_VERSION=4.3.1
@@ -18,6 +18,11 @@ import { loadavg } from "node:os";
18
18
  import { dirname, join } from "node:path";
19
19
  import { pathToFileURL } from "node:url";
20
20
  import { pruneAbandonedRuntimeScratch } from "../abandoned-runtime-scratch.mjs";
21
+ import {
22
+ bindDefaultProfileProbeCron,
23
+ buildDefaultProfileProjection,
24
+ reconcileDefaultProfileBundle,
25
+ } from "../default-profile-reconciler.mjs";
21
26
  import {
22
27
  buildNativeHermesLaunch,
23
28
  buildRuntimeServingObservation,
@@ -39,17 +44,12 @@ import {
39
44
  renewFlyCustomerControlSession,
40
45
  signedWorkerAuthorization,
41
46
  } from "../fly-runtime-identity.mjs";
47
+ import { proveDefaultProfileNativeSkills } from "../fly-skills-bridge.mjs";
42
48
  import {
43
49
  executeExternalSoulBridgeOperation,
44
50
  observeProfileSoul,
45
51
  prepareSoulLockRoot,
46
52
  } from "../fly-soul-bridge.mjs";
47
- import { proveDefaultProfileNativeSkills } from "../fly-skills-bridge.mjs";
48
- import {
49
- bindDefaultProfileProbeCron,
50
- buildDefaultProfileProjection,
51
- reconcileDefaultProfileBundle,
52
- } from "../default-profile-reconciler.mjs";
53
53
  import {
54
54
  ensureHermesSnapshotCron,
55
55
  markHermesSnapshotDirty,
@@ -105,8 +105,7 @@ const RUNTIME_GENERATION = Number(
105
105
  const BOOT_SESSION_ID = randomUUID();
106
106
  const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.4";
107
107
  const INSTALLER_PACKAGE =
108
- process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ??
109
- "@sellable/install@0.1.687";
108
+ process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.689";
110
109
  const MCP_PACKAGE =
111
110
  process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.901";
112
111
  const DEFAULT_PROFILE_BUNDLE_ROOT =
@@ -1051,6 +1050,8 @@ export function createCustomerEndpointRelay(options = {}) {
1051
1050
  getControlClient,
1052
1051
  getActiveRuntime,
1053
1052
  runtimeGeneration,
1053
+ getApiKeyFile = ({ claim }) =>
1054
+ join(SECRET_ROOT, claim.runtime.profileId, "api-server-key"),
1054
1055
  createRunsClient = ({ apiKeyFile }) =>
1055
1056
  createHermesRunsClient({ apiKeyFile }),
1056
1057
  now = () => new Date(),
@@ -1062,6 +1063,7 @@ export function createCustomerEndpointRelay(options = {}) {
1062
1063
  if (
1063
1064
  typeof getControlClient !== "function" ||
1064
1065
  typeof getActiveRuntime !== "function" ||
1066
+ typeof getApiKeyFile !== "function" ||
1065
1067
  typeof createRunsClient !== "function" ||
1066
1068
  typeof now !== "function" ||
1067
1069
  typeof sleepImpl !== "function" ||
@@ -1364,11 +1366,7 @@ export function createCustomerEndpointRelay(options = {}) {
1364
1366
  if (installedDescriptor.ok !== true) {
1365
1367
  throw new Error("customer_endpoint_mcp_descriptor_rejected");
1366
1368
  }
1367
- const apiKeyFile = join(
1368
- SECRET_ROOT,
1369
- claim.runtime.profileId,
1370
- "api-server-key"
1371
- );
1369
+ const apiKeyFile = getApiKeyFile({ claim, activeRuntime });
1372
1370
  const runs = createRunsClient({ apiKeyFile, claim, activeRuntime });
1373
1371
  const controller = new AbortController();
1374
1372
  const abortFromOuter = () => controller.abort(outerSignal.reason);
@@ -873,9 +873,38 @@ function proveCanonical(paths, raw, effectiveLoader) {
873
873
 
874
874
  function safelyRemoveLegacyRoot(paths) {
875
875
  if (!existsSync(paths.legacyRootSoulPath)) return null;
876
- const legacy = readSafeRegular(paths.legacyRootSoulPath, {
877
- maxBytes: 64 * 1024,
878
- });
876
+ let legacy;
877
+ try {
878
+ legacy = readSafeRegular(paths.legacyRootSoulPath, {
879
+ maxBytes: 64 * 1024,
880
+ });
881
+ } catch (error) {
882
+ if (error?.code !== "EACCES" && error?.code !== "EPERM") throw error;
883
+ const blocked = lstatSync(paths.legacyRootSoulPath);
884
+ if (
885
+ blocked.isSymbolicLink() ||
886
+ !blocked.isFile() ||
887
+ blocked.nlink !== 1 ||
888
+ blocked.size > 64 * 1024 ||
889
+ (blocked.mode & 0o022) !== 0 ||
890
+ !safeSharedOwner(blocked.uid)
891
+ ) {
892
+ fail("soul_legacy_root_rejected");
893
+ }
894
+ // The exact live Gateway and canonical profile SOUL were already proven
895
+ // before this cleanup runs. A root-owned 0600 legacy copy cannot be read
896
+ // by the deliberately de-privileged bridge, but the Hermes-owned data
897
+ // directory still permits removing that non-authoritative entry safely.
898
+ try {
899
+ unlinkSync(paths.legacyRootSoulPath);
900
+ fsyncDirectory(paths.dataRoot);
901
+ } catch (unlinkError) {
902
+ if (unlinkError?.code !== "EACCES" && unlinkError?.code !== "EPERM") {
903
+ throw unlinkError;
904
+ }
905
+ }
906
+ return null;
907
+ }
879
908
  if ((legacy.stat.mode & 0o022) !== 0 || !safeSharedOwner(legacy.stat.uid))
880
909
  fail("soul_legacy_root_rejected");
881
910
  const digest = sha256(legacy.bytes);
@@ -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.687";
41
+ const INSTALLER_PACKAGE = "@sellable/install@0.1.689";
42
42
  const MCP_PACKAGE = "@sellable/mcp@0.1.901";
43
43
  const HERMES_VERSION = "0.18.0";
44
44
  const SHA256 = /^[a-f0-9]{64}$/;
@@ -1547,7 +1547,7 @@ export function compileClaimToProfileDesired(activeClaim, config) {
1547
1547
  pinned.hermesCli !== "hermes" ||
1548
1548
  pinned.hermesVersion !== "0.18.0" ||
1549
1549
  pinned.installerPackage !==
1550
- "@sellable/install@0.1.687" ||
1550
+ "@sellable/install@0.1.689" ||
1551
1551
  pinned.mcpPackage !== "@sellable/mcp@0.1.901" ||
1552
1552
  !Array.isArray(policy.toolInclude) ||
1553
1553
  policy.toolInclude.length === 0 ||
@@ -283,7 +283,7 @@ function validateDesired(desired) {
283
283
  desired.hermesCli !== "hermes" ||
284
284
  desired.hermesVersion !== "0.18.0" ||
285
285
  desired.installerPackage !==
286
- "@sellable/install@0.1.687" ||
286
+ "@sellable/install@0.1.689" ||
287
287
  desired.mcpPackage !== "@sellable/mcp@0.1.901" ||
288
288
  !Array.isArray(desired.toolInclude) ||
289
289
  desired.toolInclude.length === 0 ||
@@ -26,7 +26,7 @@ import { deriveContainedProfileId } from "./profile-materializer.mjs";
26
26
 
27
27
  export const PROVISIONING_ACTION = "PROVISION_HERMES_PROFILE";
28
28
  export const PINNED_INSTALL_PACKAGE =
29
- "@sellable/install@0.1.687";
29
+ "@sellable/install@0.1.689";
30
30
  export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.901";
31
31
  const PINNED_INSTALL_VERSION = PINNED_INSTALL_PACKAGE.slice(
32
32
  PINNED_INSTALL_PACKAGE.lastIndexOf("@") + 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.687",
3
+ "version": "0.1.689",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {