@sellable/install 0.1.591 → 0.1.592-wip.runtime-gates.20260811040500

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.591
6
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.592-wip.runtime-gates.20260811040500
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.591" \
11
+ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.592-wip.runtime-gates.20260811040500" \
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.591" \
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.591' || 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.592-wip.runtime-gates.20260811040500' || 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.591', 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.592-wip.runtime-gates.20260811040500', 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.591` is
6
+ `@sellable/install@0.1.592-wip.runtime-gates.20260811040500` 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.591 .
12
+ --tag sellable-agent-host:0.1.592-wip.runtime-gates.20260811040500 .
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`.
@@ -41,7 +41,6 @@ import {
41
41
  buildAdminAgentServiceEnvironment,
42
42
  buildNativeHermesLaunch,
43
43
  } from "../admin-artifact/node_modules/@sellable/install/lib/sellable-agent/fly-customer-worker.mjs";
44
- import { expectedRuntimeGates } from "../admin-artifact/node_modules/@sellable/install/lib/sellable-agent/runtime-helper.mjs";
45
44
  import sodium from "../admin-artifact/node_modules/libsodium-wrappers/dist/modules-esm/libsodium-wrappers.mjs";
46
45
  import {
47
46
  claimWithSoulObservation,
@@ -1182,28 +1181,6 @@ async function consumeRuntimeSecrets({ claim, exchange, client }) {
1182
1181
  return { mcpService, slackApp, slackBot, modelCodex };
1183
1182
  }
1184
1183
 
1185
- function writeAdminRuntimeGates(desiredLifecycle) {
1186
- const gates = expectedRuntimeGates(desiredLifecycle);
1187
- const path = join(SECRET_ROOT, "gates.json");
1188
- atomicPrivateFile(path, `${JSON.stringify(gates)}\n`);
1189
- const stat = lstatSync(path);
1190
- let observed;
1191
- try {
1192
- observed = JSON.parse(readFileSync(path, "utf8"));
1193
- } catch {
1194
- throw new Error("admin_runtime_gates_rejected");
1195
- }
1196
- if (
1197
- stat.isSymbolicLink() ||
1198
- !stat.isFile() ||
1199
- (stat.mode & 0o777) !== 0o600 ||
1200
- canonicalJson(observed) !== canonicalJson(gates)
1201
- ) {
1202
- throw new Error("admin_runtime_gates_rejected");
1203
- }
1204
- return Object.freeze({ ...observed });
1205
- }
1206
-
1207
1184
  function adminProfileFactoryAuthJson(model) {
1208
1185
  if (model.value.schemaVersion !== "sellable-agent-model-auth-runtime/v3") {
1209
1186
  return model.hermesRootAuthJson;
@@ -1349,7 +1326,6 @@ function installRuntimeSecrets({ claim, secrets, desiredLifecycle }) {
1349
1326
  slackSecretFile,
1350
1327
  `SLACK_BOT_TOKEN=${secrets.slackBot}\nSLACK_APP_TOKEN=${secrets.slackApp}\n`
1351
1328
  );
1352
- const runtimeGates = writeAdminRuntimeGates(desiredLifecycle);
1353
1329
  const productConfigPath = join(SECRET_ROOT, "sellable-config.json");
1354
1330
  atomicPrivateFile(
1355
1331
  productConfigPath,
@@ -1368,7 +1344,6 @@ function installRuntimeSecrets({ claim, secrets, desiredLifecycle }) {
1368
1344
  modelAuthState,
1369
1345
  modelConfig: modelAuthConfigOverlay(model),
1370
1346
  productConfigPath,
1371
- runtimeGates,
1372
1347
  slackSecretFile,
1373
1348
  };
1374
1349
  }
@@ -1553,7 +1528,6 @@ function clearRuntimeSecrets() {
1553
1528
  "slack-app",
1554
1529
  "slack-bot",
1555
1530
  "profile-slack.env",
1556
- "gates.json",
1557
1531
  "sellable-config.json",
1558
1532
  ]) {
1559
1533
  rmSync(join(SECRET_ROOT, name), { force: true });
@@ -2114,7 +2088,6 @@ function completionReceipt({
2114
2088
  observedLifecycle,
2115
2089
  memorySnapshot,
2116
2090
  modelAuth,
2117
- runtimeGates,
2118
2091
  }) {
2119
2092
  const identity = operationIdentity(claim, exchange);
2120
2093
  const routeGenerationDigest = flyRuntimeDigest(
@@ -2162,9 +2135,7 @@ function completionReceipt({
2162
2135
  package: sha256(JSON.stringify(packageSpecs())),
2163
2136
  config: operation.profileContract.digest,
2164
2137
  content: observedProfileDigest,
2165
- process: sha256(
2166
- JSON.stringify({ servers: profile.servers, modelAuth, runtimeGates })
2167
- ),
2138
+ process: sha256(JSON.stringify({ servers: profile.servers, modelAuth })),
2168
2139
  };
2169
2140
  return {
2170
2141
  ...identity,
@@ -2192,7 +2163,6 @@ function completionReceipt({
2192
2163
  : "MODEL_AUTH_DESIRED_PENDING_START",
2193
2164
  status: "TRUE",
2194
2165
  },
2195
- { code: "RUNTIME_GATES_PROVEN", status: "TRUE" },
2196
2166
  { code: "SLACK_TRAFFIC_DISABLED", status: "TRUE" },
2197
2167
  { code: "NO_BOOT_INSTALL", status: "TRUE" },
2198
2168
  ],
@@ -2563,7 +2533,6 @@ export async function runAdminCanary() {
2563
2533
  },
2564
2534
  };
2565
2535
  const agentServiceEnvironment = adminAgentServiceEnvironment(canaryClaim);
2566
- const runtimeGates = writeAdminRuntimeGates("ON");
2567
2536
 
2568
2537
  const template = JSON.parse(readFileSync(ADMIN_PROFILE_TEMPLATE_PATH));
2569
2538
  const { contentDigest, ...templateContent } = template;
@@ -2761,7 +2730,6 @@ export async function runAdminCanary() {
2761
2730
  profileFactory: "stageAdminProfile",
2762
2731
  profileDigest: profileContract.digest,
2763
2732
  servers: profile.servers,
2764
- runtimeGates,
2765
2733
  workspaceAuthority: {
2766
2734
  agentKind: canaryClaim.agentKind,
2767
2735
  workspaceLockPresent:
@@ -3187,7 +3155,6 @@ async function runAdminProduction() {
3187
3155
  observedLifecycle: desiredLifecycle,
3188
3156
  memorySnapshot,
3189
3157
  modelAuth,
3190
- runtimeGates: installed.runtimeGates,
3191
3158
  });
3192
3159
  operationHeartbeat.assertHealthy();
3193
3160
  const candidateLaunch =
@@ -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.591
9
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.592-wip.runtime-gates.20260811040500
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.591";
98
+ process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.592-wip.runtime-gates.20260811040500";
99
99
  const MCP_PACKAGE =
100
100
  process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.860";
101
101
 
@@ -1838,13 +1838,6 @@ function writeRuntimeHermesConfig({
1838
1838
  },
1839
1839
  },
1840
1840
  });
1841
- const gatesPath = join(secretRoot, "gates.json");
1842
- atomicJson(gatesPath, {
1843
- lifecycle: modelLifecycle,
1844
- dispatch: modelLifecycle === "ON" ? "ENABLED" : "DISABLED",
1845
- route: modelLifecycle === "ON" ? "OPEN" : "CLOSED",
1846
- output: modelLifecycle === "ON" ? "OPEN" : "CLOSED",
1847
- });
1848
1841
  atomicJson(join(runtimeConfigRoot, "runtime-identity.json"), {
1849
1842
  runtimeId: RUNTIME_ID,
1850
1843
  profileId: desired.profileId,
@@ -1865,7 +1858,6 @@ function writeRuntimeHermesConfig({
1865
1858
  runtimeHome: runtimeConfigRoot,
1866
1859
  runtimeConfigPath,
1867
1860
  runtimeMcpPath,
1868
- gatesPath,
1869
1861
  modelBundlePath,
1870
1862
  hermesRootAuthStorePath,
1871
1863
  hermesRootAuthMarkerPath,
@@ -1966,7 +1958,6 @@ export function applicationRuntimeEnv({ desired, runtimeConfig, secrets }) {
1966
1958
  HERMES_CONFIG_PATH: runtimeConfig.runtimeConfigPath,
1967
1959
  CODEX_HOME: dirname(runtimeConfig.modelBundlePath),
1968
1960
  SELLABLE_AGENT_RUNTIME: "1",
1969
- SELLABLE_AGENT_RUNTIME_GATE_FILE: runtimeConfig.gatesPath,
1970
1961
  SELLABLE_AGENT_TURN_DESCRIPTOR_PATH: runtimeConfig.turnDescriptorPath,
1971
1962
  SLACK_HOME_CHANNEL: desired.defaultChannelId,
1972
1963
  SLACK_ALLOWED_CHANNELS: desired.selectedChannelIds.join(","),
@@ -388,7 +388,6 @@ export function buildNativeHermesLaunch({
388
388
  HERMES_PROFILE: profileId,
389
389
  HERMES_CONFIG_PATH: configPath,
390
390
  SELLABLE_AGENT_RUNTIME: "1",
391
- SELLABLE_AGENT_RUNTIME_GATE_FILE: join(resolvedSecretsRoot, "gates.json"),
392
391
  SELLABLE_SLACK_APP_TOKEN_FD: "8",
393
392
  SELLABLE_SLACK_BOT_TOKEN_FD: "9",
394
393
  }),
@@ -306,17 +306,6 @@ def _sellable_agent_call_meta():
306
306
  # identity. Send no actor meta; the serving API enforces its own actor
307
307
  # gates, as with installs outside the Sellable runtime.
308
308
  return None
309
- import json
310
- gate_path = os.getenv("SELLABLE_AGENT_RUNTIME_GATE_FILE", "")
311
- if not gate_path.startswith("/"):
312
- raise RuntimeError("Sellable runtime gate path is not pinned")
313
- try:
314
- with open(gate_path, "r", encoding="utf-8") as gate_file:
315
- gates = json.load(gate_file)
316
- except (OSError, ValueError) as exc:
317
- raise RuntimeError("Sellable runtime gates are unavailable") from exc
318
- if gates != {"lifecycle": "ON", "dispatch": "ENABLED", "route": "OPEN", "output": "OPEN"}:
319
- raise RuntimeError("Sellable runtime dispatch is fenced")
320
309
  from gateway.session_context import get_session_env, session_context_engaged
321
310
  if not session_context_engaged():
322
311
  raise RuntimeError("Sellable MCP calls require an active Hermes session context")
@@ -605,17 +594,6 @@ def _sellable_agent_call_meta():
605
594
  # identity. Send no actor meta; the serving API enforces its own actor
606
595
  # gates, as with installs outside the Sellable runtime.
607
596
  return None
608
- import json
609
- gate_path = os.getenv("SELLABLE_AGENT_RUNTIME_GATE_FILE", "")
610
- if not gate_path.startswith("/"):
611
- raise RuntimeError("Sellable runtime gate path is not pinned")
612
- try:
613
- with open(gate_path, "r", encoding="utf-8") as gate_file:
614
- gates = json.load(gate_file)
615
- except (OSError, ValueError) as exc:
616
- raise RuntimeError("Sellable runtime gates are unavailable") from exc
617
- if gates != {"lifecycle": "ON", "dispatch": "ENABLED", "route": "OPEN", "output": "OPEN"}:
618
- raise RuntimeError("Sellable runtime dispatch is fenced")
619
597
  from gateway.session_context import get_session_env, session_context_engaged
620
598
  if not session_context_engaged():
621
599
  raise RuntimeError("Sellable MCP calls require an active Hermes session context")
@@ -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.591";
42
+ "@sellable/install@0.1.592-wip.runtime-gates.20260811040500";
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.591" ||
1549
+ pinned.installerPackage !== "@sellable/install@0.1.592-wip.runtime-gates.20260811040500" ||
1550
1550
  pinned.mcpPackage !== "@sellable/mcp@0.1.860" ||
1551
1551
  !Array.isArray(policy.toolInclude) ||
1552
1552
  policy.toolInclude.length === 0 ||
@@ -2122,12 +2122,6 @@ export function createLocalPinnedActionPorts(config, dependencies = {}) {
2122
2122
  await helper({ action: "VERIFY_HERMES_BRIDGE", ...helperIdentity() });
2123
2123
  return helper({ action: "CONTROL_RUNTIME", ...helperIdentity(), control });
2124
2124
  };
2125
- const setRuntimeLifecycle = (lifecycle) =>
2126
- helper({
2127
- action: "SET_RUNTIME_GATES",
2128
- ...helperIdentity(),
2129
- lifecycle,
2130
- });
2131
2125
  const observeRuntimeLifecycle = (lifecycle) =>
2132
2126
  helper({
2133
2127
  action: "OBSERVE_RUNTIME_LIFECYCLE",
@@ -2568,10 +2562,13 @@ export function createLocalPinnedActionPorts(config, dependencies = {}) {
2568
2562
  });
2569
2563
  Object.assign(ports.lifecycle, {
2570
2564
  async fenceDispatch() {
2571
- await setRuntimeLifecycle("OFF");
2565
+ // Lifecycle authority is the service itself. Stopping it closes dispatch
2566
+ // without introducing a second file-backed switch that can strand an
2567
+ // otherwise healthy runtime.
2568
+ await controlRuntime("DOWN");
2572
2569
  },
2573
2570
  async fenceRouteOutput() {
2574
- await setRuntimeLifecycle("ARCHIVED");
2571
+ await controlRuntime("DOWN");
2575
2572
  },
2576
2573
  async drain() {
2577
2574
  await controlRuntime("DOWN");
@@ -2591,7 +2588,6 @@ export function createLocalPinnedActionPorts(config, dependencies = {}) {
2591
2588
  return observed;
2592
2589
  },
2593
2590
  async startGateway() {
2594
- await setRuntimeLifecycle("ON");
2595
2591
  await controlRuntime("UP");
2596
2592
  await observeRuntimeLifecycle("ON");
2597
2593
  },
@@ -2601,7 +2597,6 @@ export function createLocalPinnedActionPorts(config, dependencies = {}) {
2601
2597
  ? "ARCHIVING"
2602
2598
  : "OFF";
2603
2599
  await controlRuntime("DOWN");
2604
- await setRuntimeLifecycle(lifecycle);
2605
2600
  await observeRuntimeLifecycle(lifecycle);
2606
2601
  },
2607
2602
  async proveNoDispatch() {
@@ -2609,9 +2604,7 @@ export function createLocalPinnedActionPorts(config, dependencies = {}) {
2609
2604
  context.activeClaim.operation.action === "ARCHIVE" ? "ARCHIVED" : "OFF";
2610
2605
  const observed = await observeRuntimeLifecycle(lifecycle);
2611
2606
  return {
2612
- noDispatch:
2613
- observed.gates.dispatch === "FENCED" &&
2614
- observed.process.ready === false,
2607
+ noDispatch: observed.process.ready === false,
2615
2608
  };
2616
2609
  },
2617
2610
  async observeGateway(input) {
@@ -2628,7 +2621,6 @@ export function createLocalPinnedActionPorts(config, dependencies = {}) {
2628
2621
  revisionId: observed.revisionId,
2629
2622
  credentialGeneration: observed.credentialGeneration,
2630
2623
  profileDigest: observed.profileDigest,
2631
- gates: observed.gates,
2632
2624
  secretsPresent: observed.secretsPresent,
2633
2625
  process: observed.process,
2634
2626
  runtimeObservation: observed.runtimeObservation,
@@ -2679,7 +2671,6 @@ export function createLocalPinnedActionPorts(config, dependencies = {}) {
2679
2671
  };
2680
2672
  },
2681
2673
  async rollback({ priorState }) {
2682
- await setRuntimeLifecycle(priorState.observedLifecycle);
2683
2674
  if (priorState.observedLifecycle === "ON") await controlRuntime("UP");
2684
2675
  else await controlRuntime("DOWN");
2685
2676
  await observeRuntimeLifecycle(priorState.observedLifecycle);
@@ -2688,21 +2679,35 @@ export function createLocalPinnedActionPorts(config, dependencies = {}) {
2688
2679
  try {
2689
2680
  const evidence = {};
2690
2681
  const confirmedSteps = [];
2691
- let gates;
2682
+ // Older progress receipts name this step FENCE_ROUTE_OUTPUT. Its
2683
+ // concrete proof is now the stopped service, not a gates.json file.
2684
+ // Preserve the step identity so an in-flight archive can resume across
2685
+ // this runtime upgrade.
2686
+ let stopped;
2692
2687
  try {
2693
- gates = await helper({
2694
- action: "OBSERVE_RUNTIME_GATES",
2695
- ...helperIdentity(),
2696
- lifecycle: "ARCHIVED",
2697
- });
2688
+ stopped = await observeRuntimeLifecycle("ARCHIVING");
2698
2689
  } catch {
2690
+ try {
2691
+ stopped = await observeRuntimeLifecycle("ARCHIVED");
2692
+ } catch {
2693
+ return {
2694
+ ok: true,
2695
+ observationId: `archive_${canonicalHostWorkerDigest({ service: "not_stopped" })}`,
2696
+ confirmedSteps,
2697
+ };
2698
+ }
2699
+ }
2700
+ evidence.service = {
2701
+ lifecycle: stopped.lifecycle,
2702
+ processReady: stopped.process?.ready,
2703
+ };
2704
+ if (stopped.process?.ready !== false) {
2699
2705
  return {
2700
2706
  ok: true,
2701
- observationId: `archive_${canonicalHostWorkerDigest({ gates: "not_confirmed" })}`,
2707
+ observationId: `archive_${canonicalHostWorkerDigest(evidence)}`,
2702
2708
  confirmedSteps,
2703
2709
  };
2704
2710
  }
2705
- evidence.gates = gates.gates;
2706
2711
  confirmedSteps.push("FENCE_ROUTE_OUTPUT");
2707
2712
  let drain;
2708
2713
  try {
@@ -277,7 +277,7 @@ function validateDesired(desired) {
277
277
  desired.serviceCredentialGeneration < 1 ||
278
278
  desired.hermesCli !== "hermes" ||
279
279
  desired.hermesVersion !== "0.18.0" ||
280
- desired.installerPackage !== "@sellable/install@0.1.591" ||
280
+ desired.installerPackage !== "@sellable/install@0.1.592-wip.runtime-gates.20260811040500" ||
281
281
  desired.mcpPackage !== "@sellable/mcp@0.1.860" ||
282
282
  !Array.isArray(desired.toolInclude) ||
283
283
  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.591";
28
+ export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.592-wip.runtime-gates.20260811040500";
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.591" ||
614
+ manifest.installerVersion !== "0.1.592-wip.runtime-gates.20260811040500" ||
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.591",
828
+ installVersion: "0.1.592-wip.runtime-gates.20260811040500",
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.591",
876
+ installVersion: "0.1.592-wip.runtime-gates.20260811040500",
877
877
  mcpPackage: PINNED_MCP_PACKAGE,
878
878
  command: "hermes profile bootstrap",
879
879
  },
@@ -276,7 +276,6 @@ function runtimeEnvironment(contract) {
276
276
  SLACK_ALLOWED_USERS: contract.memberAllowlist.join(","),
277
277
  SLACK_REQUIRE_MENTION: "true",
278
278
  SELLABLE_AGENT_RUNTIME: "1",
279
- SELLABLE_AGENT_RUNTIME_GATE_FILE: "/runtime/gates.json",
280
279
  SELLABLE_AGENT_RUNTIME_UID: String(contract.runtime.uid),
281
280
  SELLABLE_AGENT_RUNTIME_GID: String(contract.runtime.gid),
282
281
  SELLABLE_AGENT_HERMES_EXECUTABLE: contract.paths.hermesExecutablePath,
@@ -54,8 +54,6 @@ const ACTIONS = new Set([
54
54
  "INSTALL_MCP_CREDENTIAL",
55
55
  "INSTALL_PROVIDER_CREDENTIAL",
56
56
  "INSTALL_RUNTIME_SERVICE_ENV",
57
- "SET_RUNTIME_GATES",
58
- "OBSERVE_RUNTIME_GATES",
59
57
  "OBSERVE_RUNTIME_LIFECYCLE",
60
58
  "CLEAN_PROFILE_SECRETS",
61
59
  "CHOWN_PROFILE",
@@ -103,20 +101,6 @@ const ACTION_KEYS = Object.freeze({
103
101
  "revisionId",
104
102
  "profileDigest",
105
103
  ],
106
- SET_RUNTIME_GATES: [
107
- "action",
108
- "profileId",
109
- "operationId",
110
- "fence",
111
- "lifecycle",
112
- ],
113
- OBSERVE_RUNTIME_GATES: [
114
- "action",
115
- "profileId",
116
- "operationId",
117
- "fence",
118
- "lifecycle",
119
- ],
120
104
  OBSERVE_RUNTIME_LIFECYCLE: [
121
105
  "action",
122
106
  "profileId",
@@ -781,7 +765,6 @@ function runtimeServiceEnvironmentValues({
781
765
  SELLABLE_AGENT_POLICY_HASH: manifest.policyHash,
782
766
  SELLABLE_AGENT_PROFILE_ID: profileId,
783
767
  SELLABLE_AGENT_RUNTIME_ENV_LOADED: "1",
784
- SELLABLE_AGENT_RUNTIME_GATE_FILE: "/runtime/gates.json",
785
768
  SELLABLE_AGENT_RUNTIME_SECRET_ROOT: mcpRoot,
786
769
  SELLABLE_AGENT_SELECTED_CHANNEL_IDS: JSON.stringify(
787
770
  manifest.slackChannelIds
@@ -804,56 +787,6 @@ function runtimeServiceEnvironmentValues({
804
787
  };
805
788
  }
806
789
 
807
- export function expectedRuntimeGates(lifecycle) {
808
- if (lifecycle === "ON")
809
- return { lifecycle, dispatch: "ENABLED", route: "OPEN", output: "OPEN" };
810
- if (lifecycle === "OFF")
811
- return { lifecycle, dispatch: "FENCED", route: "OPEN", output: "OPEN" };
812
- if (["ARCHIVING", "ARCHIVED"].includes(lifecycle)) {
813
- return {
814
- lifecycle: "ARCHIVED",
815
- dispatch: "FENCED",
816
- route: "FENCED",
817
- output: "FENCED",
818
- };
819
- }
820
- throw new Error("runtime lifecycle rejected");
821
- }
822
-
823
- function writeRuntimeGates(profile, lifecycle) {
824
- const root = safeRuntimeStateRoot(profile);
825
- const gates = expectedRuntimeGates(lifecycle);
826
- const pathValue = join(root, "gates.json");
827
- atomicCredential(
828
- pathValue,
829
- JSON.stringify(gates),
830
- profile.runtimeUid,
831
- profile.runtimeGid
832
- );
833
- const parsed = JSON.parse(readFileSync(pathValue, "utf8"));
834
- if (stableJson(parsed) !== stableJson(gates))
835
- throw new Error("runtime gates readback rejected");
836
- return { path: pathValue, gates };
837
- }
838
-
839
- function observeRuntimeGates(profile, lifecycle) {
840
- const root = safeRuntimeStateRoot(profile);
841
- const pathValue = join(root, "gates.json");
842
- const link = lstatSync(pathValue);
843
- const parsed = JSON.parse(readFileSync(pathValue, "utf8"));
844
- const expected = expectedRuntimeGates(lifecycle);
845
- if (
846
- link.isSymbolicLink() ||
847
- !link.isFile() ||
848
- link.uid !== profile.runtimeUid ||
849
- link.gid !== profile.runtimeGid ||
850
- (link.mode & 0o777) !== 0o600 ||
851
- stableJson(parsed) !== stableJson(expected)
852
- )
853
- throw new Error("runtime gates observation rejected");
854
- return { path: pathValue, gates: parsed };
855
- }
856
-
857
790
  function safePinnedFile(pathValue, label) {
858
791
  const resolved = resolve(pathValue ?? "");
859
792
  if (!isAbsolute(resolved) || !existsSync(resolved))
@@ -1425,7 +1358,6 @@ function installRuntimeServiceEnvironment(profile, request) {
1425
1358
  if (!existsSync(target)) throw new Error("active profile absent");
1426
1359
  const manifest = validateActiveManifest(target, request, profile);
1427
1360
  const runtimeStateRoot = safeRuntimeStateRoot(profile);
1428
- writeRuntimeGates(profile, "ON");
1429
1361
  const { paths, values } = runtimeServiceEnvironmentValues({
1430
1362
  profile,
1431
1363
  profileId: request.profileId,
@@ -1980,15 +1912,6 @@ export async function runRuntimeHelperRequest({ requestPath, config }) {
1980
1912
  if (request.action === "INSTALL_RUNTIME_SERVICE_ENV") {
1981
1913
  return installRuntimeServiceEnvironment(profile, request);
1982
1914
  }
1983
- if (request.action === "SET_RUNTIME_GATES") {
1984
- const written = writeRuntimeGates(profile, request.lifecycle);
1985
- return {
1986
- ok: true,
1987
- profileId: request.profileId,
1988
- lifecycle: request.lifecycle,
1989
- ...written,
1990
- };
1991
- }
1992
1915
  if (request.action === "CLEAN_PROFILE_SECRETS") {
1993
1916
  for (const configured of [
1994
1917
  profile.workerRuntimeSecretRoot,
@@ -2078,12 +2001,7 @@ export async function runRuntimeHelperRequest({ requestPath, config }) {
2078
2001
  if (["OBSERVE_RUNTIME", "OBSERVE_RUNTIME_DRIFT"].includes(request.action)) {
2079
2002
  return { ok: true, observation: await observeRuntime(profile, request) };
2080
2003
  }
2081
- if (request.action === "OBSERVE_RUNTIME_GATES") {
2082
- const observed = observeRuntimeGates(profile, request.lifecycle);
2083
- return { ok: true, profileId: request.profileId, ...observed };
2084
- }
2085
2004
  if (request.action === "OBSERVE_RUNTIME_LIFECYCLE") {
2086
- const gateObservation = observeRuntimeGates(profile, request.lifecycle);
2087
2005
  const { target } = profileTarget(
2088
2006
  profile,
2089
2007
  { ...request, slot: "ACTIVE" },
@@ -2116,7 +2034,6 @@ export async function runRuntimeHelperRequest({ requestPath, config }) {
2116
2034
  revisionId: observation.revisionId,
2117
2035
  credentialGeneration: observation.serviceCredential.generation,
2118
2036
  profileDigest: observation.profileDigest,
2119
- gates: gateObservation.gates,
2120
2037
  secretsPresent,
2121
2038
  process: observation.process,
2122
2039
  runtimeObservation: observation,
@@ -2149,7 +2066,6 @@ export async function runRuntimeHelperRequest({ requestPath, config }) {
2149
2066
  revisionId: manifest.revisionId,
2150
2067
  credentialGeneration: manifest.serviceCredentialGeneration,
2151
2068
  profileDigest: manifest.profileDigest,
2152
- gates: gateObservation.gates,
2153
2069
  secretsPresent,
2154
2070
  process: {
2155
2071
  ready: false,
@@ -502,29 +502,12 @@ function finalizeSuccess(state, operation, status) {
502
502
  }
503
503
 
504
504
  function observationMatches(observation, operation, lifecycle) {
505
- const expectedGates =
506
- lifecycle === "ON"
507
- ? { lifecycle: "ON", dispatch: "ENABLED", route: "OPEN", output: "OPEN" }
508
- : lifecycle === "OFF"
509
- ? {
510
- lifecycle: "OFF",
511
- dispatch: "FENCED",
512
- route: "OPEN",
513
- output: "OPEN",
514
- }
515
- : {
516
- lifecycle: "ARCHIVED",
517
- dispatch: "FENCED",
518
- route: "FENCED",
519
- output: "FENCED",
520
- };
521
505
  return Boolean(
522
506
  observation &&
523
507
  observation.lifecycle === lifecycle &&
524
508
  observation.revisionId === operation.targetRevisionId &&
525
509
  observation.credentialGeneration === operation.credentialGeneration &&
526
510
  observation.profileDigest === operation.profileDigest &&
527
- stableJson(observation.gates) === stableJson(expectedGates) &&
528
511
  observation.secretsPresent === (lifecycle !== "ARCHIVED") &&
529
512
  observation.process &&
530
513
  observation.process.ready === (lifecycle === "ON") &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.591",
3
+ "version": "0.1.592-wip.runtime-gates.20260811040500",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {