@sellable/install 0.1.590 → 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.
- package/container/Dockerfile +4 -4
- package/container/README.md +2 -2
- package/lib/sellable-agent/fly-admin-image/admin-runtime.mjs +1 -34
- package/lib/sellable-agent/fly-customer-image/Dockerfile +1 -1
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +1 -10
- package/lib/sellable-agent/fly-customer-worker.mjs +0 -1
- package/lib/sellable-agent/hermes-bridge.mjs +1 -23
- package/lib/sellable-agent/host-bootstrap.mjs +1 -1
- package/lib/sellable-agent/host-worker.mjs +29 -24
- package/lib/sellable-agent/profile-materializer.mjs +1 -1
- package/lib/sellable-agent/provisioning-adapter.mjs +4 -4
- package/lib/sellable-agent/runtime-boundary.mjs +0 -1
- package/lib/sellable-agent/runtime-helper.mjs +0 -84
- package/lib/sellable-agent/runtime-reconciler.mjs +0 -17
- package/package.json +1 -1
package/container/Dockerfile
CHANGED
|
@@ -3,12 +3,12 @@ FROM ${HERMES_IMAGE}
|
|
|
3
3
|
|
|
4
4
|
USER root
|
|
5
5
|
|
|
6
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
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.
|
|
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.590" \
|
|
|
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.
|
|
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.
|
|
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
|
package/container/README.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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")
|
|
@@ -1946,7 +1924,7 @@ export const HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT = normalizeContract({
|
|
|
1946
1924
|
sourceSha256:
|
|
1947
1925
|
"a0e136367b64007d7b49ea006ab0aa7dcc66b12134b512a463a03bd69fb8a90c",
|
|
1948
1926
|
patchedSha256:
|
|
1949
|
-
"
|
|
1927
|
+
"cd9dc106b741f3351550f21f5349c42c909b3bd938155d2e5d391e857e1e3f46",
|
|
1950
1928
|
},
|
|
1951
1929
|
{
|
|
1952
1930
|
path: "hermes_cli/web_routers/tools.py",
|
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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") &&
|