@sellable/install 0.1.608-wip.phase188.20260812.1 → 0.1.608

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,13 +3,13 @@ FROM ${HERMES_IMAGE}
3
3
 
4
4
  USER root
5
5
 
6
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.608-wip.phase188.20260812.1
7
- ARG MCP_PACKAGE=@sellable/mcp@0.1.864
6
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.608
7
+ ARG MCP_PACKAGE=@sellable/mcp@0.1.865
8
8
  ARG INSTALLER_INTEGRITY
9
- ARG MCP_INTEGRITY=sha512-UevVd71zQL2FgnqYaeHBkTi73iUV0Lg5BchM5JNhUbvaFxAFQ2M1n9uSS18qVuM9Km0aGZ7a0k8M9e2AVPboAw==
9
+ ARG MCP_INTEGRITY=sha512-282pkYFADFrrIXyQcdUpQwakXiwe/AFUDpDZkTh29CFcGHnN7dvC0MCvaU4LffXA+ZuKCpuf02uxCLGiIEOjbw==
10
10
 
11
- RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.608-wip.phase188.20260812.1" \
12
- && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.864" \
11
+ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.608" \
12
+ && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.865" \
13
13
  && test -n "${INSTALLER_INTEGRITY}" \
14
14
  && test "$(npm view "${INSTALLER_PACKAGE}" dist.integrity)" = "${INSTALLER_INTEGRITY}" \
15
15
  && test "$(npm view "${MCP_PACKAGE}" dist.integrity)" = "${MCP_INTEGRITY}" \
@@ -23,9 +23,9 @@ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.608-wip.phase188.202608
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.608-wip.phase188.20260812.1' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.864') 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.608-wip.hermes-desktop.20260811160530' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.865') 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.608-wip.phase188.20260812.1', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.864', 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.608', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.865', 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.608-wip.phase188.20260812.1` is
6
+ `@sellable/install@0.1.608` 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.608-wip.phase188.20260812.1-wip.hermes-desktop.20260811160530 .
12
+ --tag sellable-agent-host:0.1.608-wip.hermes-desktop.20260811160530 .
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`.
@@ -80,30 +80,6 @@ export function validateAdminRuntimeTuple(tuple) {
80
80
  };
81
81
  }
82
82
  }
83
- if (tuple.profileContractTemplate !== undefined) {
84
- const templatePackages = tuple.profileContractTemplate?.packages;
85
- const profilePackageByTupleName = {
86
- "@sellable/mcp": templatePackages?.sellable?.mcp,
87
- "@sellable/install": templatePackages?.sellable?.install,
88
- "@sellable/admin-mcp": templatePackages?.["sellable-admin"]?.mcp,
89
- "@sellable/admin-install": templatePackages?.["sellable-admin"]?.install,
90
- };
91
- for (const name of ADMIN_RUNTIME_TUPLE_MEMBERS) {
92
- const tuplePackage = tuple.packages[name];
93
- const profilePackage = profilePackageByTupleName[name];
94
- if (
95
- profilePackage?.name !== tuplePackage.name ||
96
- profilePackage?.version !== tuplePackage.version ||
97
- profilePackage?.integrity !== tuplePackage.integrity
98
- ) {
99
- return {
100
- ok: false,
101
- code: "admin_runtime_tuple_profile_package_mismatch",
102
- packageName: name,
103
- };
104
- }
105
- }
106
- }
107
83
  if (
108
84
  !SHA256.test(tuple.lockedTreeDigest ?? "") ||
109
85
  !SHA256.test(tuple.artifactDigest ?? "")
@@ -28,7 +28,7 @@ import {
28
28
 
29
29
  const MANIFEST_VERSION = "sellable-agent-external-runtime-closure/v1";
30
30
  const MCP_PACKAGE = "@sellable/mcp";
31
- const MCP_VERSION = "0.1.864";
31
+ const MCP_VERSION = "0.1.865";
32
32
  const SAFE_COMPONENT = /^[A-Za-z0-9@._+-]+$/;
33
33
  const HERMES_EXCLUDES = new Set([".git", ".playwright", "node_modules"]);
34
34
 
@@ -4,9 +4,9 @@ FROM ${HERMES_IMAGE}
4
4
 
5
5
  USER root
6
6
 
7
- ARG MCP_PACKAGE=@sellable/mcp@0.1.864
8
- ARG MCP_INTEGRITY=sha512-UevVd71zQL2FgnqYaeHBkTi73iUV0Lg5BchM5JNhUbvaFxAFQ2M1n9uSS18qVuM9Km0aGZ7a0k8M9e2AVPboAw==
9
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.608-wip.phase188.20260812.1
7
+ ARG MCP_PACKAGE=@sellable/mcp@0.1.865
8
+ ARG MCP_INTEGRITY=sha512-282pkYFADFrrIXyQcdUpQwakXiwe/AFUDpDZkTh29CFcGHnN7dvC0MCvaU4LffXA+ZuKCpuf02uxCLGiIEOjbw==
9
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.608
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
@@ -96,9 +96,9 @@ const RUNTIME_GENERATION = Number(
96
96
  const BOOT_SESSION_ID = randomUUID();
97
97
  const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.0";
98
98
  const INSTALLER_PACKAGE =
99
- process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.608-wip.phase188.20260812.1";
99
+ process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.608";
100
100
  const MCP_PACKAGE =
101
- process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.864";
101
+ process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.865";
102
102
 
103
103
  const sha256 = (value) => createHash("sha256").update(value).digest("hex");
104
104
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -259,10 +259,6 @@ function compileDesired(claim, trustRootPem) {
259
259
  if (
260
260
  !operation ||
261
261
  operation.targetRevisionId !== claim?.revision?.id ||
262
- !Number.isSafeInteger(operation.configGeneration) ||
263
- operation.configGeneration < 1 ||
264
- !Number.isSafeInteger(operation.lifecycleGeneration) ||
265
- operation.lifecycleGeneration < 0 ||
266
262
  !slack ||
267
263
  !service ||
268
264
  !model ||
@@ -2141,83 +2137,26 @@ async function startNativeGateway({ desired, runtimeConfig, secrets }) {
2141
2137
 
2142
2138
  export const API_SERVER_READINESS_DEADLINE_MS = 240_000;
2143
2139
  const API_SERVER_READINESS_RETRY_MS = 1_000;
2144
- export const NATIVE_GATEWAY_SERVING_PROOF_INTERVAL_MS = 30_000;
2145
- export const NATIVE_GATEWAY_RECOVERY_COOLDOWN_MS = 60_000;
2146
- export const NATIVE_GATEWAY_RECOVERY_MAX_ATTEMPTS = 3;
2147
- const NATIVE_GATEWAY_RECOVERY_BUDGET_MS = 30_000;
2148
- const NATIVE_GATEWAY_RECOVERY_BACKOFF_MS = 500;
2149
- const NATIVE_GATEWAY_RECOVERY_BACKOFF_CAP_MS = 2_000;
2150
-
2151
- const NATIVE_GATEWAY_SERVING_REASONS = new Set([
2152
- "GATEWAY_STOPPED",
2153
- "SLACK_DISCONNECTED",
2154
- "API_UNAVAILABLE",
2155
- "AUTH_UNAVAILABLE",
2156
- "CAPABILITIES_UNAVAILABLE",
2157
- "IDENTITY_MISMATCH",
2158
- "RECOVERY_FAILED",
2159
- "RECOVERY_IN_PROGRESS",
2160
- ]);
2161
-
2162
- function nativeGatewayServingFailure(reasonCode) {
2163
- const error = new Error(
2164
- `customer_native_gateway_serving_rejected:${reasonCode}`
2165
- );
2166
- error.reasonCode = reasonCode;
2167
- return error;
2168
- }
2169
-
2170
- function nativeGatewayServingFailureReason(error) {
2171
- return NATIVE_GATEWAY_SERVING_REASONS.has(error?.reasonCode)
2172
- ? error.reasonCode
2173
- : "RECOVERY_FAILED";
2174
- }
2175
-
2176
- function throwIfAborted(signal) {
2177
- if (!signal?.aborted) return;
2178
- throw signal.reason instanceof Error
2179
- ? signal.reason
2180
- : new DOMException("Aborted", "AbortError");
2181
- }
2182
-
2183
- function nativeGatewayRuntimeStatusReason(
2184
- status,
2185
- { expectedPid, expectedHome }
2186
- ) {
2187
- if (!status) return "API_UNAVAILABLE";
2188
- if (
2189
- !Number.isSafeInteger(expectedPid) ||
2190
- expectedPid <= 1 ||
2191
- status.pid !== expectedPid ||
2192
- status.kind !== "hermes-gateway" ||
2193
- !Array.isArray(status.argv) ||
2194
- !status.argv.includes("gateway") ||
2195
- !status.argv.includes("run") ||
2196
- !Number.isSafeInteger(status.start_time) ||
2197
- status.start_time <= 0 ||
2198
- status.hermes_home !== expectedHome
2199
- ) {
2200
- return "IDENTITY_MISMATCH";
2201
- }
2202
- if (status.gateway_state !== "running") return "GATEWAY_STOPPED";
2203
- if (status.platforms?.slack?.state !== "connected") {
2204
- return "SLACK_DISCONNECTED";
2205
- }
2206
- if (status.platforms?.api_server?.state !== "connected") {
2207
- return "API_UNAVAILABLE";
2208
- }
2209
- return null;
2210
- }
2211
2140
 
2212
2141
  export function nativeGatewayRuntimeStatusAccepted(
2213
2142
  status,
2214
2143
  { expectedPid, expectedHome }
2215
2144
  ) {
2216
- return (
2217
- nativeGatewayRuntimeStatusReason(status, {
2218
- expectedPid,
2219
- expectedHome,
2220
- }) === null
2145
+ return Boolean(
2146
+ status &&
2147
+ Number.isSafeInteger(expectedPid) &&
2148
+ expectedPid > 1 &&
2149
+ status.pid === expectedPid &&
2150
+ status.kind === "hermes-gateway" &&
2151
+ Array.isArray(status.argv) &&
2152
+ status.argv.includes("gateway") &&
2153
+ status.argv.includes("run") &&
2154
+ Number.isSafeInteger(status.start_time) &&
2155
+ status.start_time > 0 &&
2156
+ status.hermes_home === expectedHome &&
2157
+ status.gateway_state === "running" &&
2158
+ status.platforms?.slack?.state === "connected" &&
2159
+ status.platforms?.api_server?.state === "connected"
2221
2160
  );
2222
2161
  }
2223
2162
 
@@ -2240,140 +2179,86 @@ function readNativeGatewayRuntimeStatus(runtimeHome) {
2240
2179
  }
2241
2180
  }
2242
2181
 
2243
- export async function proveNativeGatewayServing({
2182
+ async function proveApiServerReadiness({
2244
2183
  desired,
2245
2184
  apiServerKey,
2246
2185
  gateway,
2247
2186
  runtimeConfig,
2248
- signal,
2249
- fetchImpl = fetch,
2250
- readStatus = readNativeGatewayRuntimeStatus,
2251
2187
  }) {
2252
2188
  const origin = "http://127.0.0.1:8642";
2253
- const requestSignal = () =>
2254
- signal
2255
- ? AbortSignal.any([signal, AbortSignal.timeout(5_000)])
2256
- : AbortSignal.timeout(5_000);
2257
2189
  const request = (authorization) =>
2258
- fetchImpl(`${origin}/v1/capabilities`, {
2190
+ fetch(`${origin}/v1/capabilities`, {
2259
2191
  headers: authorization ? { authorization } : {},
2260
- signal: requestSignal(),
2192
+ signal: AbortSignal.timeout(5_000),
2261
2193
  });
2262
- throwIfAborted(signal);
2263
- const gatewayReadback = gateway?.readback();
2264
- if (
2265
- !gatewayReadback?.running ||
2266
- !Number.isSafeInteger(gateway?.child?.pid) ||
2267
- gateway.child.pid <= 1
2268
- ) {
2269
- throw nativeGatewayServingFailure("GATEWAY_STOPPED");
2270
- }
2271
- const statusReason = nativeGatewayRuntimeStatusReason(
2272
- readStatus(runtimeConfig.runtimeHome),
2273
- {
2274
- expectedPid: gateway.child.pid,
2275
- expectedHome: runtimeConfig.runtimeHome,
2276
- }
2277
- );
2278
- if (statusReason) throw nativeGatewayServingFailure(statusReason);
2279
- let health;
2280
- let missing;
2281
- let wrong;
2282
- let correct;
2283
- try {
2284
- [health, missing, wrong, correct] = await Promise.all([
2285
- fetchImpl(`${origin}/health`, { signal: requestSignal() }),
2286
- request(null),
2287
- request("Bearer phase166-readiness-intentionally-wrong"),
2288
- request(`Bearer ${apiServerKey}`),
2289
- ]);
2290
- } catch (error) {
2291
- throwIfAborted(signal);
2292
- throw nativeGatewayServingFailure("API_UNAVAILABLE");
2293
- }
2294
- throwIfAborted(signal);
2295
- if (health.status !== 200) {
2296
- throw nativeGatewayServingFailure("API_UNAVAILABLE");
2297
- }
2298
- if (
2299
- missing.status !== 401 ||
2300
- wrong.status !== 401 ||
2301
- correct.status !== 200
2302
- ) {
2303
- throw nativeGatewayServingFailure("AUTH_UNAVAILABLE");
2304
- }
2305
- let capabilities;
2306
- try {
2307
- capabilities = await correct.json();
2308
- } catch {
2309
- throw nativeGatewayServingFailure("CAPABILITIES_UNAVAILABLE");
2310
- }
2311
- const finalStatusReason = nativeGatewayRuntimeStatusReason(
2312
- readStatus(runtimeConfig.runtimeHome),
2313
- {
2314
- expectedPid: gateway.child.pid,
2315
- expectedHome: runtimeConfig.runtimeHome,
2316
- }
2317
- );
2318
- if (finalStatusReason) {
2319
- throw nativeGatewayServingFailure(finalStatusReason);
2320
- }
2321
- if (
2322
- capabilities?.auth?.required !== true ||
2323
- capabilities?.features?.run_submission !== true ||
2324
- capabilities?.features?.run_events_sse !== true
2325
- ) {
2326
- throw nativeGatewayServingFailure("CAPABILITIES_UNAVAILABLE");
2327
- }
2328
- return Object.freeze({
2329
- host: "127.0.0.1",
2330
- port: 8642,
2331
- cors: false,
2332
- keyGeneration: desired.apiServerKeyGeneration,
2333
- keyFingerprint: desired.apiServerKeyFingerprint,
2334
- authStatusDigest: sha256(
2335
- JSON.stringify({
2336
- health: health.status,
2337
- missing: missing.status,
2338
- wrong: wrong.status,
2339
- correct: correct.status,
2340
- })
2341
- ),
2342
- capabilitiesDigest: sha256(JSON.stringify(capabilities)),
2343
- fixtureDigest: HERMES_RUNS_FIXTURE_IDENTITY.fixtureDigest,
2344
- relayCapability: "fixture_bound_runs_relay_v1",
2345
- runtimeGeneration: desired.runtimeGeneration,
2346
- gatewayPid: gateway.child.pid,
2347
- });
2348
- }
2349
-
2350
- async function proveApiServerReadiness({
2351
- desired,
2352
- apiServerKey,
2353
- gateway,
2354
- runtimeConfig,
2355
- signal,
2356
- }) {
2357
2194
  const deadlineAt = Date.now() + API_SERVER_READINESS_DEADLINE_MS;
2358
2195
  while (Date.now() < deadlineAt) {
2196
+ const gatewayReadback = gateway?.readback();
2197
+ if (!gatewayReadback?.running) {
2198
+ throw new Error(
2199
+ `customer_native_gateway_start_rejected:${redactRuntimeDiagnostic(
2200
+ gatewayReadback?.output ?? "candidate exited before readiness"
2201
+ )}`
2202
+ );
2203
+ }
2204
+ const candidateStatus = readNativeGatewayRuntimeStatus(
2205
+ runtimeConfig.runtimeHome
2206
+ );
2207
+ if (
2208
+ !nativeGatewayRuntimeStatusAccepted(candidateStatus, {
2209
+ expectedPid: gateway.child.pid,
2210
+ expectedHome: runtimeConfig.runtimeHome,
2211
+ })
2212
+ ) {
2213
+ await sleep(API_SERVER_READINESS_RETRY_MS);
2214
+ continue;
2215
+ }
2359
2216
  try {
2360
- return await proveNativeGatewayServing({
2361
- desired,
2362
- apiServerKey,
2363
- gateway,
2364
- runtimeConfig,
2365
- signal,
2366
- });
2367
- } catch (error) {
2368
- throwIfAborted(signal);
2369
- if (nativeGatewayServingFailureReason(error) === "GATEWAY_STOPPED") {
2370
- const gatewayReadback = gateway?.readback();
2371
- throw new Error(
2372
- `customer_native_gateway_start_rejected:${redactRuntimeDiagnostic(
2373
- gatewayReadback?.output ?? "candidate exited before readiness"
2374
- )}`
2375
- );
2217
+ const [health, missing, wrong, correct] = await Promise.all([
2218
+ fetch(`${origin}/health`, { signal: AbortSignal.timeout(5_000) }),
2219
+ request(null),
2220
+ request("Bearer phase166-readiness-intentionally-wrong"),
2221
+ request(`Bearer ${apiServerKey}`),
2222
+ ]);
2223
+ const capabilities = correct.ok ? await correct.json() : null;
2224
+ if (
2225
+ health.status === 200 &&
2226
+ missing.status === 401 &&
2227
+ wrong.status === 401 &&
2228
+ correct.status === 200 &&
2229
+ nativeGatewayRuntimeStatusAccepted(
2230
+ readNativeGatewayRuntimeStatus(runtimeConfig.runtimeHome),
2231
+ {
2232
+ expectedPid: gateway.child.pid,
2233
+ expectedHome: runtimeConfig.runtimeHome,
2234
+ }
2235
+ ) &&
2236
+ capabilities?.auth?.required === true &&
2237
+ capabilities?.features?.run_submission === true &&
2238
+ capabilities?.features?.run_events_sse === true
2239
+ ) {
2240
+ return Object.freeze({
2241
+ host: "127.0.0.1",
2242
+ port: 8642,
2243
+ cors: false,
2244
+ keyGeneration: desired.apiServerKeyGeneration,
2245
+ keyFingerprint: desired.apiServerKeyFingerprint,
2246
+ authStatusDigest: sha256(
2247
+ JSON.stringify({
2248
+ health: health.status,
2249
+ missing: missing.status,
2250
+ wrong: wrong.status,
2251
+ correct: correct.status,
2252
+ })
2253
+ ),
2254
+ capabilitiesDigest: sha256(JSON.stringify(capabilities)),
2255
+ fixtureDigest: HERMES_RUNS_FIXTURE_IDENTITY.fixtureDigest,
2256
+ relayCapability: "fixture_bound_runs_relay_v1",
2257
+ runtimeGeneration: desired.runtimeGeneration,
2258
+ gatewayPid: gateway.child.pid,
2259
+ });
2376
2260
  }
2261
+ } catch {
2377
2262
  // Hermes can hold its singleton lock before the local HTTP listener is
2378
2263
  // accepting connections. Keep the lifecycle claim fenced while the
2379
2264
  // bounded readiness window converges instead of replacing it again.
@@ -2390,12 +2275,10 @@ export async function startProvenNativeGateway({
2390
2275
  startGateway = startNativeGateway,
2391
2276
  proveReadiness = proveApiServerReadiness,
2392
2277
  stopGateway = stopNativeGateway,
2393
- signal,
2394
2278
  }) {
2395
2279
  let gateway = null;
2396
2280
  try {
2397
- gateway = await startGateway({ desired, runtimeConfig, secrets, signal });
2398
- throwIfAborted(signal);
2281
+ gateway = await startGateway({ desired, runtimeConfig, secrets });
2399
2282
  const gatewayReadback = gateway.readback();
2400
2283
  if (!gatewayReadback.running) {
2401
2284
  throw new Error("customer_native_gateway_readback_rejected");
@@ -2405,7 +2288,6 @@ export async function startProvenNativeGateway({
2405
2288
  apiServerKey: secrets.hermesApiServer,
2406
2289
  gateway,
2407
2290
  runtimeConfig,
2408
- signal,
2409
2291
  });
2410
2292
  return { gateway, gatewayReadback, apiServerReadiness };
2411
2293
  } catch (error) {
@@ -2428,230 +2310,6 @@ export async function startProvenNativeGateway({
2428
2310
  }
2429
2311
  }
2430
2312
 
2431
- function nativeGatewayServingObservation({
2432
- runtime,
2433
- bootSessionId,
2434
- flyGeneration,
2435
- state,
2436
- reasonCode,
2437
- checkedAt,
2438
- }) {
2439
- const gatewayPid = runtime.gateway?.child?.pid;
2440
- const desired = runtime.desired;
2441
- const authority = runtime.servingAuthority;
2442
- if (
2443
- !Number.isSafeInteger(gatewayPid) ||
2444
- gatewayPid <= 1 ||
2445
- authority?.runtimeId !== desired.runtimeId ||
2446
- authority?.profileId !== desired.profileId ||
2447
- !Number.isSafeInteger(authority?.runtimeGeneration) ||
2448
- authority.runtimeGeneration < 1 ||
2449
- !Number.isSafeInteger(authority?.configGeneration) ||
2450
- authority.configGeneration < 1 ||
2451
- !Number.isSafeInteger(authority?.lifecycleGeneration) ||
2452
- authority.lifecycleGeneration < 0
2453
- ) {
2454
- throw new Error("customer_native_gateway_observation_rejected");
2455
- }
2456
- return Object.freeze({
2457
- version: 1,
2458
- runtimeId: authority.runtimeId,
2459
- flyGeneration,
2460
- runtimeGeneration: authority.runtimeGeneration,
2461
- configGeneration: authority.configGeneration,
2462
- lifecycleGeneration: authority.lifecycleGeneration,
2463
- bootSessionId,
2464
- profileId: desired.profileId,
2465
- hermesHomeId: flyRuntimeDigest({
2466
- schemaVersion: "sellable-agent-hermes-home/v1",
2467
- root: DATA_ROOT,
2468
- profileId: desired.profileId,
2469
- }),
2470
- gatewayPid,
2471
- checkedAt: new Date(checkedAt).toISOString(),
2472
- state,
2473
- reasonCode,
2474
- });
2475
- }
2476
-
2477
- /**
2478
- * One in-process, single-flight owner beside the existing signed claim loop.
2479
- * It creates no timer: the lifecycle loop decides when to call poll().
2480
- */
2481
- export function createNativeGatewayServingSupervisor({
2482
- runtime,
2483
- getActiveRuntime,
2484
- bootSessionId,
2485
- flyGeneration,
2486
- now = Date.now,
2487
- random = Math.random,
2488
- pause = (ms, signal) => abortablePause(ms, signal, sleep),
2489
- proveServing = proveNativeGatewayServing,
2490
- startProvenGateway = startProvenNativeGateway,
2491
- stopGateway = stopNativeGateway,
2492
- }) {
2493
- let stopped = false;
2494
- let episode = null;
2495
- let episodeController = null;
2496
- let nextEligibleAt = now() + NATIVE_GATEWAY_SERVING_PROOF_INTERVAL_MS;
2497
- let current = null;
2498
- const outgoing = [];
2499
-
2500
- const authorityCurrent = () =>
2501
- !stopped &&
2502
- getActiveRuntime() === runtime &&
2503
- runtime.servingAuthority?.flyGeneration === flyGeneration;
2504
-
2505
- const publish = (state, reasonCode) => {
2506
- if (!authorityCurrent()) return null;
2507
- const observation = nativeGatewayServingObservation({
2508
- runtime,
2509
- bootSessionId,
2510
- flyGeneration,
2511
- state,
2512
- reasonCode,
2513
- checkedAt: now(),
2514
- });
2515
- current = observation;
2516
- if (state !== "READY") {
2517
- while (outgoing[0]?.state === "READY") outgoing.shift();
2518
- }
2519
- const previous = outgoing.at(-1);
2520
- if (
2521
- previous?.state === observation.state &&
2522
- previous?.reasonCode === observation.reasonCode &&
2523
- previous?.gatewayPid === observation.gatewayPid
2524
- ) {
2525
- outgoing[outgoing.length - 1] = observation;
2526
- } else {
2527
- outgoing.push(observation);
2528
- }
2529
- return observation;
2530
- };
2531
-
2532
- publish("READY", null);
2533
-
2534
- const runEpisode = async (signal) => {
2535
- let failureReason = "RECOVERY_FAILED";
2536
- try {
2537
- await proveServing({
2538
- desired: runtime.desired,
2539
- apiServerKey: runtime.secrets.hermesApiServer,
2540
- gateway: runtime.gateway,
2541
- runtimeConfig: runtime.runtimeConfig,
2542
- signal,
2543
- });
2544
- if (!authorityCurrent()) return;
2545
- publish("READY", null);
2546
- nextEligibleAt = now() + NATIVE_GATEWAY_SERVING_PROOF_INTERVAL_MS;
2547
- return;
2548
- } catch (error) {
2549
- throwIfAborted(signal);
2550
- failureReason = nativeGatewayServingFailureReason(error);
2551
- }
2552
-
2553
- publish("RECOVERING", "RECOVERY_IN_PROGRESS");
2554
- const deadlineAt = now() + NATIVE_GATEWAY_RECOVERY_BUDGET_MS;
2555
- for (
2556
- let attempt = 1;
2557
- attempt <= NATIVE_GATEWAY_RECOVERY_MAX_ATTEMPTS;
2558
- attempt += 1
2559
- ) {
2560
- const backoff = Math.round(
2561
- Math.min(
2562
- NATIVE_GATEWAY_RECOVERY_BACKOFF_CAP_MS,
2563
- NATIVE_GATEWAY_RECOVERY_BACKOFF_MS * 2 ** (attempt - 1)
2564
- ) *
2565
- (0.75 + random() * 0.5)
2566
- );
2567
- await pause(backoff, signal);
2568
- throwIfAborted(signal);
2569
- if (!authorityCurrent()) return;
2570
- if (now() >= deadlineAt) {
2571
- failureReason = "RECOVERY_FAILED";
2572
- break;
2573
- }
2574
- try {
2575
- await stopGateway(runtime.gateway);
2576
- throwIfAborted(signal);
2577
- if (!authorityCurrent()) return;
2578
- if (now() >= deadlineAt) {
2579
- failureReason = "RECOVERY_FAILED";
2580
- break;
2581
- }
2582
- const proven = await startProvenGateway({
2583
- desired: runtime.desired,
2584
- runtimeConfig: runtime.runtimeConfig,
2585
- secrets: runtime.secrets,
2586
- signal,
2587
- proveReadiness: (input) => proveServing({ ...input, signal }),
2588
- });
2589
- throwIfAborted(signal);
2590
- if (!authorityCurrent()) {
2591
- await stopGateway(proven.gateway);
2592
- return;
2593
- }
2594
- if (now() >= deadlineAt) {
2595
- await stopGateway(proven.gateway);
2596
- failureReason = "RECOVERY_FAILED";
2597
- break;
2598
- }
2599
- runtime.gateway = proven.gateway;
2600
- publish("READY", null);
2601
- nextEligibleAt = now() + NATIVE_GATEWAY_SERVING_PROOF_INTERVAL_MS;
2602
- return;
2603
- } catch (error) {
2604
- throwIfAborted(signal);
2605
- failureReason = nativeGatewayServingFailureReason(error);
2606
- }
2607
- }
2608
- if (!authorityCurrent()) return;
2609
- publish("DEGRADED", failureReason);
2610
- nextEligibleAt = now() + NATIVE_GATEWAY_RECOVERY_COOLDOWN_MS;
2611
- };
2612
-
2613
- return Object.freeze({
2614
- pendingObservation() {
2615
- return outgoing[0] ?? current;
2616
- },
2617
- observationSent(observation) {
2618
- if (outgoing[0] === observation) outgoing.shift();
2619
- },
2620
- poll({ force = false } = {}) {
2621
- if (
2622
- stopped ||
2623
- episode ||
2624
- !authorityCurrent() ||
2625
- (!force && now() < nextEligibleAt)
2626
- ) {
2627
- return episode;
2628
- }
2629
- episodeController = new AbortController();
2630
- const activeEpisode = runEpisode(episodeController.signal)
2631
- .catch((error) => {
2632
- if (error?.name !== "AbortError") throw error;
2633
- })
2634
- .finally(() => {
2635
- if (episode === activeEpisode) {
2636
- episode = null;
2637
- episodeController = null;
2638
- }
2639
- });
2640
- episode = activeEpisode;
2641
- return activeEpisode;
2642
- },
2643
- async stop() {
2644
- stopped = true;
2645
- episodeController?.abort();
2646
- try {
2647
- await episode;
2648
- } catch (error) {
2649
- if (error?.name !== "AbortError") throw error;
2650
- }
2651
- },
2652
- });
2653
- }
2654
-
2655
2313
  async function proveProvisionedApplication({
2656
2314
  desired,
2657
2315
  runtimeConfig,
@@ -3158,14 +2816,6 @@ async function processOperation({ claim, exchange, client, identity }) {
3158
2816
  runtimeConfig,
3159
2817
  trustRootPem,
3160
2818
  action: claim.operation.action,
3161
- servingAuthority: Object.freeze({
3162
- runtimeId: RUNTIME_ID,
3163
- flyGeneration: RUNTIME_GENERATION,
3164
- runtimeGeneration: RUNTIME_GENERATION,
3165
- configGeneration: claim.operation.configGeneration,
3166
- lifecycleGeneration: claim.operation.lifecycleGeneration,
3167
- profileId: desired.profileId,
3168
- }),
3169
2819
  gateway: proof.gateway ?? null,
3170
2820
  modelHealthReporter,
3171
2821
  };
@@ -3215,7 +2865,6 @@ async function main() {
3215
2865
  });
3216
2866
  const controller = new AbortController();
3217
2867
  let activeRuntime = null;
3218
- let servingSupervisor = null;
3219
2868
  let shutdownMemorySnapshot = null;
3220
2869
  const endpointRelay = createCustomerEndpointRelay({
3221
2870
  getControlClient: () => client,
@@ -3229,8 +2878,6 @@ async function main() {
3229
2878
  const runtime = activeRuntime;
3230
2879
  shutdownMemorySnapshot = (async () => {
3231
2880
  try {
3232
- await servingSupervisor?.stop();
3233
- servingSupervisor = null;
3234
2881
  await endpointRelay.fenceActive("RUNTIME_SHUTDOWN");
3235
2882
  await stopNativeGateway(runtime.gateway);
3236
2883
  runtime.gateway = null;
@@ -3272,10 +2919,6 @@ async function main() {
3272
2919
  callerProfileId: activeRuntime.desired.profileId,
3273
2920
  }),
3274
2921
  leaseSeconds: 60,
3275
- servingObservation:
3276
- servingSupervisor?.pendingObservation() ?? undefined,
3277
- onServingObservationSent: (observation) =>
3278
- servingSupervisor?.observationSent(observation),
3279
2922
  })
3280
2923
  : await client.claim(60);
3281
2924
  } catch (error) {
@@ -3320,12 +2963,20 @@ async function main() {
3320
2963
  ),
3321
2964
  });
3322
2965
  }
3323
- void servingSupervisor?.poll();
2966
+ if (
2967
+ activeRuntime?.action !== "INSTANTIATE" &&
2968
+ activeRuntime?.gateway &&
2969
+ !activeRuntime.gateway.readback().running
2970
+ ) {
2971
+ activeRuntime.gateway = await startNativeGateway({
2972
+ desired: activeRuntime.desired,
2973
+ runtimeConfig: activeRuntime.runtimeConfig,
2974
+ secrets: activeRuntime.secrets,
2975
+ });
2976
+ }
3324
2977
  await sleep(1_000);
3325
2978
  continue;
3326
2979
  }
3327
- await servingSupervisor?.stop();
3328
- servingSupervisor = null;
3329
2980
  operationHeartbeat = startOperationHeartbeat({
3330
2981
  claim,
3331
2982
  exchange,
@@ -3340,15 +2991,6 @@ async function main() {
3340
2991
  client,
3341
2992
  identity,
3342
2993
  });
3343
- servingSupervisor =
3344
- activeRuntime.action === "INSTANTIATE"
3345
- ? null
3346
- : createNativeGatewayServingSupervisor({
3347
- runtime: activeRuntime,
3348
- getActiveRuntime: () => activeRuntime,
3349
- bootSessionId: BOOT_SESSION_ID,
3350
- flyGeneration: RUNTIME_GENERATION,
3351
- });
3352
2994
  soulClaimTracker = createSoulClaimObservationTracker({
3353
2995
  runtimeId: RUNTIME_ID,
3354
2996
  profileId: activeRuntime.desired.profileId,
@@ -784,8 +784,6 @@ export async function claimWithSoulObservation({
784
784
  tracker,
785
785
  readSoul,
786
786
  leaseSeconds = 60,
787
- servingObservation,
788
- onServingObservationSent,
789
787
  }) {
790
788
  // Deliver lifecycle work before any potentially slow local loader proof.
791
789
  // Reconciliation uses the same cadence only after the server confirms that
@@ -793,9 +791,7 @@ export async function claimWithSoulObservation({
793
791
  const tick = await client.claimTick({
794
792
  leaseSeconds,
795
793
  soulObservation: tracker.pending(),
796
- ...(servingObservation ? { servingObservation } : {}),
797
794
  });
798
- if (servingObservation) onServingObservationSent?.(servingObservation);
799
795
  if (tick?.claim) return tick.claim;
800
796
 
801
797
  const acknowledgement = tick?.soulAcknowledgement;
@@ -1078,15 +1074,10 @@ export function createFlyCustomerControlClient({
1078
1074
  }
1079
1075
  return { ...result, signedUrl: undefined, bytes };
1080
1076
  };
1081
- const claimTick = async ({
1082
- leaseSeconds = 60,
1083
- soulObservation,
1084
- servingObservation,
1085
- } = {}) =>
1077
+ const claimTick = async ({ leaseSeconds = 60, soulObservation } = {}) =>
1086
1078
  post("claim", "/api/v3/sellable-agent/worker/claim", {
1087
1079
  leaseSeconds,
1088
1080
  ...(soulObservation ? { soulObservation } : {}),
1089
- ...(servingObservation ? { servingObservation } : {}),
1090
1081
  });
1091
1082
  return Object.freeze({
1092
1083
  claimTick,
@@ -38,8 +38,8 @@ 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.608-wip.phase188.20260812.1";
42
- const MCP_PACKAGE = "@sellable/mcp@0.1.864";
41
+ const INSTALLER_PACKAGE = "@sellable/install@0.1.608";
42
+ const MCP_PACKAGE = "@sellable/mcp@0.1.865";
43
43
  const HERMES_VERSION = "0.18.0";
44
44
  const SHA256 = /^[a-f0-9]{64}$/;
45
45
  const SHA256_16 = /^[a-f0-9]{16}$/;
@@ -1546,8 +1546,8 @@ 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.608-wip.phase188.20260812.1" ||
1550
- pinned.mcpPackage !== "@sellable/mcp@0.1.864" ||
1549
+ pinned.installerPackage !== "@sellable/install@0.1.608" ||
1550
+ pinned.mcpPackage !== "@sellable/mcp@0.1.865" ||
1551
1551
  !Array.isArray(policy.toolInclude) ||
1552
1552
  policy.toolInclude.length === 0 ||
1553
1553
  !Number.isSafeInteger(slack.generation) ||
@@ -278,8 +278,8 @@ 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.608-wip.phase188.20260812.1" ||
282
- desired.mcpPackage !== "@sellable/mcp@0.1.864" ||
281
+ desired.installerPackage !== "@sellable/install@0.1.608" ||
282
+ desired.mcpPackage !== "@sellable/mcp@0.1.865" ||
283
283
  !Array.isArray(desired.toolInclude) ||
284
284
  desired.toolInclude.length === 0 ||
285
285
  desired.toolInclude.some((tool) => !/^[a-z][a-z0-9_]{0,127}$/.test(tool)) ||
@@ -25,8 +25,8 @@ 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.608-wip.phase188.20260812.1";
29
- export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.864";
28
+ export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.608";
29
+ export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.865";
30
30
  const PINNED_INSTALL_VERSION = PINNED_INSTALL_PACKAGE.slice(
31
31
  PINNED_INSTALL_PACKAGE.lastIndexOf("@") + 1
32
32
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.608-wip.phase188.20260812.1",
3
+ "version": "0.1.608",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {
@@ -5,7 +5,9 @@ visibility: public
5
5
  allowed-tools:
6
6
  - mcp__sellable__refill_sends
7
7
  - mcp__sellable__refill_v3_advance
8
+ - mcp__sellable__refill_v3_world_state
8
9
  - mcp__sellable__refill_v3_continue
10
+ - mcp__sellable__get_campaigns
9
11
  - mcp__sellable__get_refill_target_plan
10
12
  - mcp__sellable__get_scheduler_fill_capacity
11
13
  - mcp__sellable__get_subskill_asset
@@ -604,6 +606,19 @@ Synchronous source, configuration, rubric, and exhaustion authorities already
604
606
  return after their own exact reread, so they return `settlement.ready:true` from
605
607
  that bounded authority receipt and never wait on unrelated campaign-wide
606
608
  processing cells.
609
+
610
+ When a non-yolo request names a campaign, resolve that visible name with one
611
+ authorized bounded `get_campaigns` call carrying an explicit `limit`. Require
612
+ exactly one case-insensitive exact match; if none or more than one match, stop
613
+ without preview or mutation. Retain only the returned stable campaign ID. Call
614
+ `refill_v3_world_state({workspaceId, scope, campaignId})`, render that exact
615
+ campaign's two-lane readiness and its explicit zero product side effects, then
616
+ stop at the product-native question with exactly Accept and Decline. Do not call
617
+ `refill_v3_advance` before Accept. Accept calls
618
+ `refill_v3_advance({workspaceId, scope, campaignId})`; Decline stops. Carry the
619
+ same campaignId through every advance, continue, replay, partial-wait, and
620
+ post-wait repeat. Display-name changes never alter this retained identity, and
621
+ no sibling campaign may replace it.
607
622
  Call the next advance only when `settlement.ready` is true. When it is false,
608
623
  call `wait_for_campaign_processing` with `settlement.resumeInput` **unchanged**;
609
624
  that carries the exact `preparationJobId`, `requirePreparationTerminal:true`,