@sellable/install 0.1.652 → 0.1.654-wip.phase189.20260814.2

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.652
6
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.654-wip.phase189.20260814.2
7
7
  ARG MCP_PACKAGE=@sellable/mcp@0.1.880
8
8
  ARG INSTALLER_INTEGRITY
9
9
  ARG MCP_INTEGRITY=sha512-+Fo8aQlYduLaQMQff+ZA25LeUhWZ7I7UH5DECpND84LQ+rHSjKeKFj4Zs93bVHXwQCIy2OLbr75y4f4+CyT57w==
10
10
 
11
- RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.652" \
11
+ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.654-wip.phase189.20260814.2" \
12
12
  && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.880" \
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.652" \
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.652' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.880') 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.654-wip.phase189.20260814.2' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.880') 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.652', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.880', 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.654-wip.phase189.20260814.2', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.880', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
29
29
  && npm cache clean --force
30
30
 
31
31
  COPY entrypoint.sh /usr/local/bin/sellable-agent-container-entrypoint
@@ -3,7 +3,7 @@
3
3
  This image is a dedicated Agent worker/runtime boundary beside an existing Hermes dashboard. It never replaces or reconfigures the dashboard container.
4
4
 
5
5
  Build from this directory after
6
- `@sellable/install@0.1.652` is
6
+ `@sellable/install@0.1.654-wip.phase189.20260814.2` is
7
7
  published:
8
8
 
9
9
  ```sh
@@ -148,6 +148,7 @@ RUN --mount=type=secret,id=mcp_trust_root,required=true \
148
148
  test "$(sha256sum /usr/local/share/sellable-agent/mcp-turn-trust-root.pem | cut -d ' ' -f 1)" = "${MCP_TRUST_ROOT_FINGERPRINT}"
149
149
 
150
150
  COPY fly-admin-image/admin-runtime.mjs /usr/local/lib/sellable-agent/admin-runtime/admin-runtime.mjs
151
+ COPY fly-admin-image/admin-runtime-phase-telemetry.mjs /usr/local/lib/sellable-agent/admin-runtime/admin-runtime-phase-telemetry.mjs
151
152
  COPY fly-admin-image/admin-state-generation.mjs /usr/local/lib/sellable-agent/admin-runtime/admin-state-generation.mjs
152
153
  COPY fly-admin-image/start-admin-runtime /usr/local/lib/sellable-agent/admin-runtime/start-admin-runtime
153
154
  COPY admin-native-gateway-supervisor.mjs /usr/local/lib/sellable-agent/admin-native-gateway-supervisor.mjs
@@ -0,0 +1,80 @@
1
+ const ADMIN_RUNTIME_PHASES = new Set([
2
+ "runtime_secrets",
3
+ "snapshot_restore",
4
+ "profile_settings_delivery",
5
+ "profile_stage",
6
+ "profile_live_merge_promote",
7
+ "profile_crons",
8
+ "default_profile_bundle",
9
+ "memory_snapshot",
10
+ "slack_serving_proof",
11
+ "native_gateway",
12
+ "serving_observation",
13
+ "operation_completion",
14
+ ]);
15
+
16
+ function phaseFailureCode(error) {
17
+ const code = error instanceof Error ? error.message : "";
18
+ return /^[A-Za-z0-9_:-]{1,160}$/.test(code)
19
+ ? code
20
+ : "admin_runtime_phase_failed";
21
+ }
22
+
23
+ export function createAdminRuntimePhaseTelemetry({
24
+ bootSessionId,
25
+ operationId,
26
+ now = () => Date.now(),
27
+ write = (entry) => process.stderr.write(`${JSON.stringify(entry)}\n`),
28
+ }) {
29
+ if (
30
+ typeof bootSessionId !== "string" ||
31
+ bootSessionId.length < 1 ||
32
+ typeof operationId !== "string" ||
33
+ operationId.length < 1
34
+ ) {
35
+ throw new Error("admin_runtime_phase_identity_rejected");
36
+ }
37
+
38
+ const run = async (phase, work) => {
39
+ if (!ADMIN_RUNTIME_PHASES.has(phase) || typeof work !== "function") {
40
+ throw new Error("admin_runtime_phase_rejected");
41
+ }
42
+ const startedAt = now();
43
+ write({
44
+ ok: true,
45
+ code: "admin_runtime_phase",
46
+ phase,
47
+ status: "started",
48
+ elapsedMs: 0,
49
+ bootSessionId,
50
+ operationId,
51
+ });
52
+ try {
53
+ const result = await work();
54
+ write({
55
+ ok: true,
56
+ code: "admin_runtime_phase",
57
+ phase,
58
+ status: "completed",
59
+ elapsedMs: Math.max(0, now() - startedAt),
60
+ bootSessionId,
61
+ operationId,
62
+ });
63
+ return result;
64
+ } catch (error) {
65
+ write({
66
+ ok: false,
67
+ code: "admin_runtime_phase",
68
+ phase,
69
+ status: "failed",
70
+ elapsedMs: Math.max(0, now() - startedAt),
71
+ bootSessionId,
72
+ operationId,
73
+ errorCode: phaseFailureCode(error),
74
+ });
75
+ throw error;
76
+ }
77
+ };
78
+
79
+ return { run };
80
+ }
@@ -103,6 +103,7 @@ import {
103
103
  reconcileHermesModelAuth,
104
104
  } from "../model-auth-reconciler.mjs";
105
105
  import { createModelHealthReporter } from "../model-health-reporter.mjs";
106
+ import { createAdminRuntimePhaseTelemetry } from "./admin-runtime-phase-telemetry.mjs";
106
107
  import {
107
108
  completeAdminStateReceiptRecovery,
108
109
  prepareAdminStateGeneration,
@@ -1004,6 +1005,7 @@ async function materializeAdminProfile({
1004
1005
  soul,
1005
1006
  slack = null,
1006
1007
  agentServiceEnv = {},
1008
+ runPhase = async (_phase, work) => work(),
1007
1009
  }) {
1008
1010
  const profileHome = profileContract.profileHome;
1009
1011
  const profile = basename(profileHome);
@@ -1030,91 +1032,99 @@ async function materializeAdminProfile({
1030
1032
  const layout = adminHomeLayout(profileHome);
1031
1033
  // stageAdminProfile canonicalizes these two paths before staging, so the
1032
1034
  // Admin home working copies must exist before the factory runs.
1033
- deliverAdminSettings(settingsRoot, layout);
1035
+ await runPhase("profile_settings_delivery", async () => {
1036
+ deliverAdminSettings(settingsRoot, layout);
1037
+ });
1034
1038
  const serverProofs = {};
1035
- const stageWithAuthority = async (stagedAuthority) => {
1036
- const stage = await stageAdminProfile({
1037
- mode: slack ? "direct_soul_native_slack" : "direct_soul",
1038
- lifecycleAuthorized: slack ? true : undefined,
1039
- ephemeralProductConfig: Boolean(slack),
1040
- ephemeralSlackSecrets: Boolean(slack),
1041
- profile,
1042
- profilesRoot: PROFILES_ROOT,
1043
- stageRoot,
1044
- finalProfileRoot: profileHome,
1045
- rootAuthPath,
1046
- canonicalAdminConfigPath: layout.configPath,
1047
- canonicalAdminStatePath: layout.statePath,
1048
- productConfigPath,
1049
- model: "gpt-5.6-sol",
1050
- reasoningEffort: "high",
1051
- soul: stagedAuthority,
1052
- ...(slack
1053
- ? {
1054
- slackSecretFile: slack.secretFile,
1055
- teamId: slack.teamId,
1056
- primaryChannel: slack.primaryChannel,
1057
- secondaryReadChannelIds: slack.secondaryReadChannelIds,
1058
- allowedSlackUserIds: slack.allowedSlackUserIds,
1059
- slackApp: slack.slackApp,
1039
+ const stageWithAuthority = async (stagedAuthority) =>
1040
+ runPhase("profile_stage", async () => {
1041
+ const stage = await stageAdminProfile({
1042
+ mode: slack ? "direct_soul_native_slack" : "direct_soul",
1043
+ lifecycleAuthorized: slack ? true : undefined,
1044
+ ephemeralProductConfig: Boolean(slack),
1045
+ ephemeralSlackSecrets: Boolean(slack),
1046
+ profile,
1047
+ profilesRoot: PROFILES_ROOT,
1048
+ stageRoot,
1049
+ finalProfileRoot: profileHome,
1050
+ rootAuthPath,
1051
+ canonicalAdminConfigPath: layout.configPath,
1052
+ canonicalAdminStatePath: layout.statePath,
1053
+ productConfigPath,
1054
+ model: "gpt-5.6-sol",
1055
+ reasoningEffort: "high",
1056
+ soul: stagedAuthority,
1057
+ ...(slack
1058
+ ? {
1059
+ slackSecretFile: slack.secretFile,
1060
+ teamId: slack.teamId,
1061
+ primaryChannel: slack.primaryChannel,
1062
+ secondaryReadChannelIds: slack.secondaryReadChannelIds,
1063
+ allowedSlackUserIds: slack.allowedSlackUserIds,
1064
+ slackApp: slack.slackApp,
1065
+ }
1066
+ : {}),
1067
+ skillsRoot: join(ADMIN_INSTALL_ASSET_ROOT, "mcp", "skills"),
1068
+ installRoot: layout.adminRoot,
1069
+ adminHome: layout.adminHome,
1070
+ installAdmin: async () =>
1071
+ profilePackageReceipt(
1072
+ profileContract,
1073
+ "sellable-admin",
1074
+ "mcp",
1075
+ layout
1076
+ ),
1077
+ installProduct: async () =>
1078
+ profilePackageReceipt(
1079
+ profileContract,
1080
+ "sellable",
1081
+ "mcp",
1082
+ layout,
1083
+ agentServiceEnv
1084
+ ),
1085
+ verifyMcp: async ({ kind, profileRoot, config }) => {
1086
+ const alias = kind === "admin" ? "sellable-admin" : "sellable";
1087
+ const server = config.mcp_servers[alias];
1088
+ const inventoryProof =
1089
+ alias === "sellable" ? productMcpInventoryProofEnvironment() : null;
1090
+ let proof;
1091
+ try {
1092
+ proof = await inspectMcp({
1093
+ alias,
1094
+ command: server.command,
1095
+ args: server.args,
1096
+ env: {
1097
+ HOME: profileRoot,
1098
+ HERMES_HOME: profileRoot,
1099
+ ...adminToolEnv(layout),
1100
+ ...agentServiceEnv,
1101
+ SELLABLE_CONFIG_PATH: productConfigPath,
1102
+ ...(server.env ?? {}),
1103
+ ...(inventoryProof?.env ?? {}),
1104
+ },
1105
+ packageProof: profileContract.packages[alias].mcp,
1106
+ });
1107
+ } finally {
1108
+ if (inventoryProof) {
1109
+ rmSync(inventoryProof.root, { recursive: true, force: true });
1110
+ }
1060
1111
  }
1061
- : {}),
1062
- skillsRoot: join(ADMIN_INSTALL_ASSET_ROOT, "mcp", "skills"),
1063
- installRoot: layout.adminRoot,
1064
- adminHome: layout.adminHome,
1065
- installAdmin: async () =>
1066
- profilePackageReceipt(profileContract, "sellable-admin", "mcp", layout),
1067
- installProduct: async () =>
1068
- profilePackageReceipt(
1069
- profileContract,
1070
- "sellable",
1071
- "mcp",
1072
- layout,
1073
- agentServiceEnv
1074
- ),
1075
- verifyMcp: async ({ kind, profileRoot, config }) => {
1076
- const alias = kind === "admin" ? "sellable-admin" : "sellable";
1077
- const server = config.mcp_servers[alias];
1078
- const inventoryProof =
1079
- alias === "sellable" ? productMcpInventoryProofEnvironment() : null;
1080
- let proof;
1081
- try {
1082
- proof = await inspectMcp({
1083
- alias,
1084
- command: server.command,
1085
- args: server.args,
1086
- env: {
1087
- HOME: profileRoot,
1088
- HERMES_HOME: profileRoot,
1089
- ...adminToolEnv(layout),
1090
- ...agentServiceEnv,
1091
- SELLABLE_CONFIG_PATH: productConfigPath,
1092
- ...(server.env ?? {}),
1093
- ...(inventoryProof?.env ?? {}),
1094
- },
1095
- packageProof: profileContract.packages[alias].mcp,
1096
- });
1097
- } finally {
1098
- if (inventoryProof) {
1099
- rmSync(inventoryProof.root, { recursive: true, force: true });
1112
+ if (
1113
+ JSON.stringify(proof.tools) !==
1114
+ JSON.stringify(profileContract.tools[alias])
1115
+ ) {
1116
+ throw new Error("admin_runtime_tool_inventory_mismatch");
1100
1117
  }
1101
- }
1102
- if (
1103
- JSON.stringify(proof.tools) !==
1104
- JSON.stringify(profileContract.tools[alias])
1105
- ) {
1106
- throw new Error("admin_runtime_tool_inventory_mismatch");
1107
- }
1108
- serverProofs[alias] = proof;
1109
- return { ok: true, ...proof };
1110
- },
1118
+ serverProofs[alias] = proof;
1119
+ return { ok: true, ...proof };
1120
+ },
1121
+ });
1122
+ if (slack) installWritableAdminSlackChannelPolicy(stageRoot, slack);
1123
+ installAdminModelConfig(stageRoot, modelConfig);
1124
+ installAdminHermesMemoryHook(stageRoot);
1125
+ stageAdminRuntimeTree(stageRoot);
1126
+ return stage;
1111
1127
  });
1112
- if (slack) installWritableAdminSlackChannelPolicy(stageRoot, slack);
1113
- installAdminModelConfig(stageRoot, modelConfig);
1114
- installAdminHermesMemoryHook(stageRoot);
1115
- stageAdminRuntimeTree(stageRoot);
1116
- return stage;
1117
- };
1118
1128
 
1119
1129
  let stagedAuthority = withProfileSoulLock(profile, readAuthoritativeSoul, {
1120
1130
  dataRoot: DATA_ROOT,
@@ -1123,79 +1133,83 @@ async function materializeAdminProfile({
1123
1133
  let promoted = null;
1124
1134
  for (let attempt = 0; attempt < 2 && !promoted; attempt += 1) {
1125
1135
  stage = await stageWithAuthority(stagedAuthority);
1126
- const committed = await withProfileSoulLock(
1127
- profile,
1128
- async () => {
1129
- const finalAuthority = readAuthoritativeSoul();
1130
- if (
1131
- finalAuthority.sha256 !== stagedAuthority.sha256 ||
1132
- !finalAuthority.bytes.equals(stagedAuthority.bytes)
1133
- ) {
1134
- rmSync(stageRoot, { recursive: true, force: true });
1135
- return { status: "RESTAGE", stagedAuthority: finalAuthority };
1136
- }
1137
- const liveSkillsIdentity = adminPreservedSkillsIdentity(profileHome);
1138
- if (liveSkillsIdentity !== null) {
1139
- replaceHermesState(
1140
- join(profileHome, "skills"),
1141
- join(stageRoot, "skills"),
1142
- {
1143
- preserveTargetTopLevelNames: [...ADMIN_SEALED_SKILL_DIRECTORIES],
1144
- }
1145
- );
1146
- }
1147
- mergeHermesState(profileHome, stageRoot, {
1148
- skipSourceTopLevelNames:
1149
- liveSkillsIdentity === null ? [] : ["skills"],
1150
- });
1151
- mergeAdminLiveSkillsConfig(profileHome, stageRoot);
1152
- installAdminBrowserToolsetDefault(profileHome, stageRoot);
1153
- if (
1154
- liveSkillsIdentity !== null &&
1155
- adminPreservedSkillsIdentity(stageRoot) !== liveSkillsIdentity
1156
- ) {
1157
- rmSync(stageRoot, { recursive: true, force: true });
1158
- return { status: "RESTAGE", stagedAuthority: finalAuthority };
1159
- }
1160
- const stagedSoulPath = join(stageRoot, "SOUL.md");
1161
- const stagedSoul = lstatSync(stagedSoulPath);
1162
- const stagedSoulBytes = readFileSync(stagedSoulPath);
1163
- if (
1164
- stagedSoul.isSymbolicLink() ||
1165
- !stagedSoul.isFile() ||
1166
- !stagedSoulBytes.equals(finalAuthority.bytes)
1167
- ) {
1168
- throw new Error("admin_runtime_staged_soul_rejected");
1169
- }
1170
- const promotion = await promoteAdminProfileAtomically({
1171
- profile,
1172
- currentRoot: profileHome,
1173
- stagedRoot: stageRoot,
1174
- backupRoot,
1175
- });
1176
- const finalSoulPath = join(profileHome, "SOUL.md");
1177
- const finalSoul = lstatSync(finalSoulPath);
1178
- const finalSoulBytes = readFileSync(finalSoulPath);
1179
- if (
1180
- finalSoul.isSymbolicLink() ||
1181
- !finalSoul.isFile() ||
1182
- finalSoul.uid !== (process.getuid?.() ?? finalSoul.uid) ||
1183
- finalSoul.gid !== (process.getgid?.() ?? finalSoul.gid) ||
1184
- !finalSoulBytes.equals(finalAuthority.bytes)
1185
- ) {
1186
- throw new Error("admin_runtime_live_soul_readback_rejected");
1187
- }
1188
- chmodSync(finalSoulPath, 0o600);
1189
- if ((lstatSync(finalSoulPath).mode & 0o777) !== 0o600) {
1190
- throw new Error("admin_runtime_live_soul_mode_rejected");
1191
- }
1192
- return {
1193
- status: "PROMOTED",
1194
- promotion,
1195
- observedSoulRawSha256: sha256(finalSoulBytes),
1196
- };
1197
- },
1198
- { dataRoot: DATA_ROOT }
1136
+ const committed = await runPhase("profile_live_merge_promote", async () =>
1137
+ withProfileSoulLock(
1138
+ profile,
1139
+ async () => {
1140
+ const finalAuthority = readAuthoritativeSoul();
1141
+ if (
1142
+ finalAuthority.sha256 !== stagedAuthority.sha256 ||
1143
+ !finalAuthority.bytes.equals(stagedAuthority.bytes)
1144
+ ) {
1145
+ rmSync(stageRoot, { recursive: true, force: true });
1146
+ return { status: "RESTAGE", stagedAuthority: finalAuthority };
1147
+ }
1148
+ const liveSkillsIdentity = adminPreservedSkillsIdentity(profileHome);
1149
+ if (liveSkillsIdentity !== null) {
1150
+ replaceHermesState(
1151
+ join(profileHome, "skills"),
1152
+ join(stageRoot, "skills"),
1153
+ {
1154
+ preserveTargetTopLevelNames: [
1155
+ ...ADMIN_SEALED_SKILL_DIRECTORIES,
1156
+ ],
1157
+ }
1158
+ );
1159
+ }
1160
+ mergeHermesState(profileHome, stageRoot, {
1161
+ skipSourceTopLevelNames:
1162
+ liveSkillsIdentity === null ? [] : ["skills"],
1163
+ });
1164
+ mergeAdminLiveSkillsConfig(profileHome, stageRoot);
1165
+ installAdminBrowserToolsetDefault(profileHome, stageRoot);
1166
+ if (
1167
+ liveSkillsIdentity !== null &&
1168
+ adminPreservedSkillsIdentity(stageRoot) !== liveSkillsIdentity
1169
+ ) {
1170
+ rmSync(stageRoot, { recursive: true, force: true });
1171
+ return { status: "RESTAGE", stagedAuthority: finalAuthority };
1172
+ }
1173
+ const stagedSoulPath = join(stageRoot, "SOUL.md");
1174
+ const stagedSoul = lstatSync(stagedSoulPath);
1175
+ const stagedSoulBytes = readFileSync(stagedSoulPath);
1176
+ if (
1177
+ stagedSoul.isSymbolicLink() ||
1178
+ !stagedSoul.isFile() ||
1179
+ !stagedSoulBytes.equals(finalAuthority.bytes)
1180
+ ) {
1181
+ throw new Error("admin_runtime_staged_soul_rejected");
1182
+ }
1183
+ const promotion = await promoteAdminProfileAtomically({
1184
+ profile,
1185
+ currentRoot: profileHome,
1186
+ stagedRoot: stageRoot,
1187
+ backupRoot,
1188
+ });
1189
+ const finalSoulPath = join(profileHome, "SOUL.md");
1190
+ const finalSoul = lstatSync(finalSoulPath);
1191
+ const finalSoulBytes = readFileSync(finalSoulPath);
1192
+ if (
1193
+ finalSoul.isSymbolicLink() ||
1194
+ !finalSoul.isFile() ||
1195
+ finalSoul.uid !== (process.getuid?.() ?? finalSoul.uid) ||
1196
+ finalSoul.gid !== (process.getgid?.() ?? finalSoul.gid) ||
1197
+ !finalSoulBytes.equals(finalAuthority.bytes)
1198
+ ) {
1199
+ throw new Error("admin_runtime_live_soul_readback_rejected");
1200
+ }
1201
+ chmodSync(finalSoulPath, 0o600);
1202
+ if ((lstatSync(finalSoulPath).mode & 0o777) !== 0o600) {
1203
+ throw new Error("admin_runtime_live_soul_mode_rejected");
1204
+ }
1205
+ return {
1206
+ status: "PROMOTED",
1207
+ promotion,
1208
+ observedSoulRawSha256: sha256(finalSoulBytes),
1209
+ };
1210
+ },
1211
+ { dataRoot: DATA_ROOT }
1212
+ )
1199
1213
  );
1200
1214
  if (committed.status === "PROMOTED") {
1201
1215
  promoted = committed;
@@ -1208,20 +1222,27 @@ async function materializeAdminProfile({
1208
1222
  }
1209
1223
  // The installer bakes absolute paths into every generated wrapper, so bin/
1210
1224
  // is generated once the promoted Admin home is at its final path.
1211
- const adminHome = installAdminHome({ settingsRoot, layout });
1212
- const memoryCron = await ensureHermesSnapshotCron({
1213
- profileId: profile,
1214
- profileRoot: profileHome,
1215
- hermesRoot: DATA_ROOT,
1216
- hermesCli: HERMES,
1217
- });
1218
- if (!memoryCron.ok) throw new Error(memoryCron.code);
1219
- const opportunitySequencesCron = await ensureHermesOpportunitySequencesCron({
1220
- profileId: profile,
1221
- profileRoot: profileHome,
1222
- hermesRoot: DATA_ROOT,
1223
- statePath: layout.statePath,
1224
- });
1225
+ const { adminHome, memoryCron, opportunitySequencesCron } = await runPhase(
1226
+ "profile_crons",
1227
+ async () => {
1228
+ const adminHome = installAdminHome({ settingsRoot, layout });
1229
+ const memoryCron = await ensureHermesSnapshotCron({
1230
+ profileId: profile,
1231
+ profileRoot: profileHome,
1232
+ hermesRoot: DATA_ROOT,
1233
+ hermesCli: HERMES,
1234
+ });
1235
+ if (!memoryCron.ok) throw new Error(memoryCron.code);
1236
+ const opportunitySequencesCron =
1237
+ await ensureHermesOpportunitySequencesCron({
1238
+ profileId: profile,
1239
+ profileRoot: profileHome,
1240
+ hermesRoot: DATA_ROOT,
1241
+ statePath: layout.statePath,
1242
+ });
1243
+ return { adminHome, memoryCron, opportunitySequencesCron };
1244
+ }
1245
+ );
1225
1246
  return {
1226
1247
  stage,
1227
1248
  promotion: promoted.promotion,
@@ -3640,6 +3661,10 @@ async function runAdminProduction() {
3640
3661
  exchange,
3641
3662
  client,
3642
3663
  });
3664
+ const phaseTelemetry = createAdminRuntimePhaseTelemetry({
3665
+ bootSessionId: BOOT_SESSION_ID,
3666
+ operationId: claim.operation.id,
3667
+ });
3643
3668
  await activeModelHealthReporter?.stop();
3644
3669
  activeModelHealthReporter = null;
3645
3670
  await stopNativeGateway(activeGateway);
@@ -3713,30 +3738,40 @@ async function runAdminProduction() {
3713
3738
  // Authority is fully decidable from the claim and durable settings
3714
3739
  // metadata. Refuse it before consuming any lease secret so the failure
3715
3740
  // can be reported as exact and retryable instead of uncertain.
3716
- const secrets = await consumeRuntimeSecrets({
3717
- claim,
3718
- exchange,
3719
- client,
3720
- });
3721
- claimSecretsConsumed = true;
3722
3741
  const desiredLifecycle = desiredLifecycleForClaim(claim);
3723
- const installed = installRuntimeSecrets({
3724
- claim,
3725
- secrets,
3726
- desiredLifecycle,
3727
- });
3742
+ const { secrets, installed } = await phaseTelemetry.run(
3743
+ "runtime_secrets",
3744
+ async () => {
3745
+ const secrets = await consumeRuntimeSecrets({
3746
+ claim,
3747
+ exchange,
3748
+ client,
3749
+ });
3750
+ claimSecretsConsumed = true;
3751
+ const installed = installRuntimeSecrets({
3752
+ claim,
3753
+ secrets,
3754
+ desiredLifecycle,
3755
+ });
3756
+ return { secrets, installed };
3757
+ }
3758
+ );
3728
3759
  const proofOperation = operation ?? {
3729
3760
  runtimeGenerationId: claim.adminRuntimeGeneration.id,
3730
3761
  generationFence: claim.adminRuntimeGeneration.fence,
3731
3762
  profileContract,
3732
3763
  };
3733
- const memoryRestore = await restoreLatestHermesSnapshot({
3734
- profileId: basename(profileContract.profileHome),
3735
- profileRoot: profileContract.profileHome,
3736
- hermesRoot: DATA_ROOT,
3737
- hermesVersion: "0.20.0",
3738
- fetchLatest: () => client.getMemorySnapshot(),
3739
- });
3764
+ const memoryRestore = await phaseTelemetry.run(
3765
+ "snapshot_restore",
3766
+ async () =>
3767
+ restoreLatestHermesSnapshot({
3768
+ profileId: basename(profileContract.profileHome),
3769
+ profileRoot: profileContract.profileHome,
3770
+ hermesRoot: DATA_ROOT,
3771
+ hermesVersion: "0.20.0",
3772
+ fetchLatest: () => client.getMemorySnapshot(),
3773
+ })
3774
+ );
3740
3775
  if (!memoryRestore.ok) throw new Error(memoryRestore.code);
3741
3776
  await operationHeartbeat.reattest();
3742
3777
  const legacyLightfieldVenv = quarantineLegacyLightfieldVenv({
@@ -3766,6 +3801,7 @@ async function runAdminProduction() {
3766
3801
  soul: exactSoul(claim),
3767
3802
  slack: nativeSlackProfileInput(claim, installed),
3768
3803
  agentServiceEnv: adminAgentServiceEnvironment(claim),
3804
+ runPhase: phaseTelemetry.run,
3769
3805
  });
3770
3806
  await operationHeartbeat.reattest();
3771
3807
  const materializerReceipt = adminMaterializerReceipt(
@@ -3776,21 +3812,25 @@ async function runAdminProduction() {
3776
3812
  kind: "ADMIN",
3777
3813
  claim,
3778
3814
  });
3779
- const reconciledDefaultProfile = await reconcileDefaultProfileBundle({
3780
- activeProjection,
3781
- materializerReceipt,
3782
- profileRoot: profile.profileHome,
3783
- bundleRoot: DEFAULT_PROFILE_BUNDLE_ROOT,
3784
- dataRoot: DATA_ROOT,
3785
- reattestAuthority: async () => {
3786
- await operationHeartbeat.reattest();
3787
- return activeProjection;
3788
- },
3789
- cronAdapter: adminDefaultProfileCronAdapter(
3790
- profile.profile,
3791
- profile.profileHome
3792
- ),
3793
- });
3815
+ const reconciledDefaultProfile = await phaseTelemetry.run(
3816
+ "default_profile_bundle",
3817
+ async () =>
3818
+ reconcileDefaultProfileBundle({
3819
+ activeProjection,
3820
+ materializerReceipt,
3821
+ profileRoot: profile.profileHome,
3822
+ bundleRoot: DEFAULT_PROFILE_BUNDLE_ROOT,
3823
+ dataRoot: DATA_ROOT,
3824
+ reattestAuthority: async () => {
3825
+ await operationHeartbeat.reattest();
3826
+ return activeProjection;
3827
+ },
3828
+ cronAdapter: adminDefaultProfileCronAdapter(
3829
+ profile.profile,
3830
+ profile.profileHome
3831
+ ),
3832
+ })
3833
+ );
3794
3834
  if (!reconciledDefaultProfile.ok)
3795
3835
  throw new Error(reconciledDefaultProfile.code);
3796
3836
  const defaultProfile = reconciledDefaultProfile;
@@ -3813,11 +3853,15 @@ async function runAdminProduction() {
3813
3853
  profile.opportunitySequencesCron.status !== "REUSED";
3814
3854
  if (materializerChanged || defaultProfile.changed)
3815
3855
  markHermesSnapshotDirty(profile.profileHome);
3816
- const memorySnapshot = await flushHermesSnapshot({
3817
- client,
3818
- profileId: profile.profile,
3819
- profileRoot: profile.profileHome,
3820
- });
3856
+ const memorySnapshot = await phaseTelemetry.run(
3857
+ "memory_snapshot",
3858
+ async () =>
3859
+ flushHermesSnapshot({
3860
+ client,
3861
+ profileId: profile.profile,
3862
+ profileRoot: profile.profileHome,
3863
+ })
3864
+ );
3821
3865
  await operationHeartbeat.reattest();
3822
3866
  const settings = settingsProof(
3823
3867
  imported.receipt.generationRoot,
@@ -3841,10 +3885,13 @@ async function runAdminProduction() {
3841
3885
  });
3842
3886
  }
3843
3887
  operationHeartbeat.assertHealthy();
3844
- const slackProof =
3845
- desiredLifecycle === "ON"
3846
- ? await proveAdminSlackServing(claim, secrets)
3847
- : false;
3888
+ const slackProof = await phaseTelemetry.run(
3889
+ "slack_serving_proof",
3890
+ async () =>
3891
+ desiredLifecycle === "ON"
3892
+ ? proveAdminSlackServing(claim, secrets)
3893
+ : false
3894
+ );
3848
3895
  await operationHeartbeat.reattest();
3849
3896
  const candidateLaunch =
3850
3897
  desiredLifecycle === "ON"
@@ -3853,7 +3900,10 @@ async function runAdminProduction() {
3853
3900
  let candidateGateway = null;
3854
3901
  try {
3855
3902
  if (candidateLaunch) {
3856
- candidateGateway = await startNativeGateway(candidateLaunch);
3903
+ candidateGateway = await phaseTelemetry.run(
3904
+ "native_gateway",
3905
+ async () => startNativeGateway(candidateLaunch)
3906
+ );
3857
3907
  await operationHeartbeat.reattest();
3858
3908
  }
3859
3909
  const nativeDefaultProfileProof = candidateGateway
@@ -3876,26 +3926,31 @@ async function runAdminProduction() {
3876
3926
  activeProjection,
3877
3927
  nativeDefaultProfileProof,
3878
3928
  });
3879
- if (claim.runtimeAuthority && candidateLaunch && candidateGateway) {
3880
- const servingObservation = adminServingObservation(
3881
- candidateLaunch,
3882
- candidateGateway,
3883
- exchange
3884
- );
3885
- if (!servingObservation) {
3886
- throw new Error("admin_serving_observation_unavailable");
3887
- }
3888
- const serving = await client.claimTick({
3889
- leaseSeconds: 60,
3890
- servingObservation,
3891
- });
3892
- if (serving?.servingObservationStatus !== "accepted") {
3893
- throw new Error(
3894
- `admin_serving_observation_rejected:${serving?.servingObservationCode ?? serving?.servingObservationStatus ?? "unknown"}`
3929
+ await phaseTelemetry.run("serving_observation", async () => {
3930
+ if (claim.runtimeAuthority && candidateLaunch && candidateGateway) {
3931
+ const servingObservation = adminServingObservation(
3932
+ candidateLaunch,
3933
+ candidateGateway,
3934
+ exchange
3895
3935
  );
3936
+ if (!servingObservation) {
3937
+ throw new Error("admin_serving_observation_unavailable");
3938
+ }
3939
+ const serving = await client.claimTick({
3940
+ leaseSeconds: 60,
3941
+ servingObservation,
3942
+ });
3943
+ if (serving?.servingObservationStatus !== "accepted") {
3944
+ throw new Error(
3945
+ `admin_serving_observation_rejected:${serving?.servingObservationCode ?? serving?.servingObservationStatus ?? "unknown"}`
3946
+ );
3947
+ }
3896
3948
  }
3897
- }
3898
- const completed = await client.complete(receipt);
3949
+ });
3950
+ const completed = await phaseTelemetry.run(
3951
+ "operation_completion",
3952
+ async () => client.complete(receipt)
3953
+ );
3899
3954
  if (completed?.accepted !== true) {
3900
3955
  throw new Error("admin_runtime_completion_rejected");
3901
3956
  }
@@ -8,7 +8,7 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright
8
8
 
9
9
  ARG MCP_PACKAGE=@sellable/mcp@0.1.880
10
10
  ARG MCP_INTEGRITY=sha512-+Fo8aQlYduLaQMQff+ZA25LeUhWZ7I7UH5DECpND84LQ+rHSjKeKFj4Zs93bVHXwQCIy2OLbr75y4f4+CyT57w==
11
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.652
11
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.654-wip.phase189.20260814.2
12
12
  ARG ADMIN_MCP_PACKAGE=@sellable/admin-mcp@0.1.186-wip.phase158.20260802034727
13
13
  ARG ADMIN_MCP_INTEGRITY=sha512-qYNew5wd2IfgVcp1UYOG9QvpF/8Mx19YJ5HQtMKzvSfUf3evePjtaKsoXLf2C/GUJ5QlLlHJo84VWOI3V01+9A==
14
14
  ARG TUS_JS_CLIENT_VERSION=4.3.1
@@ -81,6 +81,7 @@ RUN --mount=type=secret,id=mcp_trust_root,required=true \
81
81
 
82
82
  COPY mcp-context-proxy.mjs mcp-runtime-binding.mjs fly-customer-model.mjs fly-customer-worker.mjs profile-materializer.mjs hermes-bridge.mjs hermes-memory-snapshot.mjs fly-runtime-identity.mjs fly-soul-bridge.mjs fly-soul-bridge-exec.mjs fly-skills-bridge.mjs fly-skills-bridge-exec.mjs fly-capabilities-bridge.mjs fly-capabilities-bridge-exec.mjs soul-artifact-validator.mjs default-profile-bundle.mjs default-profile-reconciler.mjs fly-cron-proof-exec.mjs model-auth-reconciler.mjs model-auth-canary-reseed.mjs model-health-reporter.mjs \
83
83
  /usr/local/lib/sellable-agent/
84
+ COPY abandoned-runtime-scratch.mjs /usr/local/lib/sellable-agent/abandoned-runtime-scratch.mjs
84
85
  COPY default-profile-bundles/shared/ /usr/local/lib/sellable-agent/default-profile-bundles/shared/
85
86
  COPY default-profile-bundles/customer/ /usr/local/lib/sellable-agent/default-profile-bundles/customer/
86
87
  COPY default-profile-bundles/fixtures/ /usr/local/lib/sellable-agent/default-profile-bundles/fixtures/
@@ -17,6 +17,7 @@ import {
17
17
  import { loadavg } from "node:os";
18
18
  import { dirname, join } from "node:path";
19
19
  import { pathToFileURL } from "node:url";
20
+ import { pruneAbandonedRuntimeScratch } from "../abandoned-runtime-scratch.mjs";
20
21
  import {
21
22
  buildNativeHermesLaunch,
22
23
  buildRuntimeServingObservation,
@@ -105,7 +106,7 @@ const BOOT_SESSION_ID = randomUUID();
105
106
  const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.0";
106
107
  const INSTALLER_PACKAGE =
107
108
  process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ??
108
- "@sellable/install@0.1.652";
109
+ "@sellable/install@0.1.654-wip.phase189.20260814.2";
109
110
  const MCP_PACKAGE =
110
111
  process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.880";
111
112
  const DEFAULT_PROFILE_BUNDLE_ROOT =
@@ -3093,6 +3094,19 @@ async function main() {
3093
3094
  ) {
3094
3095
  throw new Error("customer_configuration_rejected");
3095
3096
  }
3097
+ const scratch = pruneAbandonedRuntimeScratch({
3098
+ hermesRoot: DATA_ROOT,
3099
+ profilesRoot: PROFILES_ROOT,
3100
+ });
3101
+ if (scratch.removed.length > 0) {
3102
+ process.stderr.write(
3103
+ `${JSON.stringify({
3104
+ ok: true,
3105
+ code: scratch.code,
3106
+ removed: scratch.removed,
3107
+ })}\n`
3108
+ );
3109
+ }
3096
3110
  let oidcToken = await obtainFlyOidcToken({ audience: AUDIENCE });
3097
3111
  dropFlyRuntimePrivileges({
3098
3112
  user: "hermes",
@@ -514,7 +514,7 @@ export async function ensureHermesNamedCron({
514
514
  try {
515
515
  const before = cronJobs(profileRoot);
516
516
  const named = before.filter((job) => job?.name === name);
517
- if (named.length > 1 || (named[0] && !isUpdatable(named[0]))) {
517
+ if (named.length > 1) {
518
518
  return failed(`${codePrefix}_inventory_rejected`);
519
519
  }
520
520
  if (named[0] && isExpected(named[0])) {
@@ -525,6 +525,9 @@ export async function ensureHermesNamedCron({
525
525
  job: named[0],
526
526
  };
527
527
  }
528
+ if (named[0] && !isUpdatable(named[0])) {
529
+ return failed(`${codePrefix}_inventory_rejected`);
530
+ }
528
531
  const siblings = before.filter((job) => job?.name !== name);
529
532
  const mutation = named[0] ? await update(named[0]) : await create();
530
533
  if (childFailed(mutation)) {
@@ -38,7 +38,7 @@ import { installAgentHostServices } from "./service-installer.mjs";
38
38
  const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
39
39
  const VERSION = "sellable-agent-host-bootstrap/v1";
40
40
  const RECEIPT_VERSION = "sellable-agent-host-registration/v1";
41
- const INSTALLER_PACKAGE = "@sellable/install@0.1.652";
41
+ const INSTALLER_PACKAGE = "@sellable/install@0.1.654-wip.phase189.20260814.2";
42
42
  const MCP_PACKAGE = "@sellable/mcp@0.1.880";
43
43
  const HERMES_VERSION = "0.18.0";
44
44
  const SHA256 = /^[a-f0-9]{64}$/;
@@ -1547,7 +1547,7 @@ export function compileClaimToProfileDesired(activeClaim, config) {
1547
1547
  pinned.hermesCli !== "hermes" ||
1548
1548
  pinned.hermesVersion !== "0.18.0" ||
1549
1549
  pinned.installerPackage !==
1550
- "@sellable/install@0.1.652" ||
1550
+ "@sellable/install@0.1.654-wip.phase189.20260814.2" ||
1551
1551
  pinned.mcpPackage !== "@sellable/mcp@0.1.880" ||
1552
1552
  !Array.isArray(policy.toolInclude) ||
1553
1553
  policy.toolInclude.length === 0 ||
@@ -283,7 +283,7 @@ function validateDesired(desired) {
283
283
  desired.hermesCli !== "hermes" ||
284
284
  desired.hermesVersion !== "0.18.0" ||
285
285
  desired.installerPackage !==
286
- "@sellable/install@0.1.652" ||
286
+ "@sellable/install@0.1.654-wip.phase189.20260814.2" ||
287
287
  desired.mcpPackage !== "@sellable/mcp@0.1.880" ||
288
288
  !Array.isArray(desired.toolInclude) ||
289
289
  desired.toolInclude.length === 0 ||
@@ -26,7 +26,7 @@ import { deriveContainedProfileId } from "./profile-materializer.mjs";
26
26
 
27
27
  export const PROVISIONING_ACTION = "PROVISION_HERMES_PROFILE";
28
28
  export const PINNED_INSTALL_PACKAGE =
29
- "@sellable/install@0.1.652";
29
+ "@sellable/install@0.1.654-wip.phase189.20260814.2";
30
30
  export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.880";
31
31
  const PINNED_INSTALL_VERSION = PINNED_INSTALL_PACKAGE.slice(
32
32
  PINNED_INSTALL_PACKAGE.lastIndexOf("@") + 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.652",
3
+ "version": "0.1.654-wip.phase189.20260814.2",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {