@sellable/install 0.1.484 → 0.1.485-wip.phase169.20260804195555

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.484
6
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.485-wip.phase169.20260804195555
7
7
  ARG MCP_PACKAGE=@sellable/mcp@0.1.760-wip.phase143.20260727100513
8
8
  ARG INSTALLER_INTEGRITY
9
9
  ARG MCP_INTEGRITY=sha512-muNXHuRnLTfsnW+MQUvK+p0VBZETDKeevr70iRliXyqE3/SLrZQJLL1laJzMo+qiUs9jMXMHdKOQfQqLiwJvtg==
10
10
 
11
- RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.484" \
11
+ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.485-wip.phase169.20260804195555" \
12
12
  && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.760-wip.phase143.20260727100513" \
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.484" \
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.484' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.760-wip.phase143.20260727100513') 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.485-wip.phase169.20260804195555' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.760-wip.phase143.20260727100513') 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.484', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.760-wip.phase143.20260727100513', 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.485-wip.phase169.20260804195555', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.760-wip.phase143.20260727100513', 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.484` is
6
+ `@sellable/install@0.1.485-wip.phase169.20260804195555` 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.484 .
12
+ --tag sellable-agent-host:0.1.485-wip.phase169.20260804195555 .
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`.
@@ -81,6 +81,8 @@ COPY fly-admin-image/start-admin-runtime /usr/local/lib/sellable-agent/admin-run
81
81
  COPY fly-runtime-identity.mjs /usr/local/lib/sellable-agent/fly-runtime-identity.mjs
82
82
  COPY hermes-bridge.mjs /usr/local/lib/sellable-agent/hermes-bridge.mjs
83
83
  COPY hermes-memory-snapshot.mjs /usr/local/lib/sellable-agent/hermes-memory-snapshot.mjs
84
+ COPY fly-soul-bridge.mjs /usr/local/lib/sellable-agent/fly-soul-bridge.mjs
85
+ COPY soul-artifact-validator.mjs /usr/local/lib/sellable-agent/soul-artifact-validator.mjs
84
86
  COPY hermes-memory-dirty.mjs /opt/sellable/bin/hermes-memory-dirty
85
87
  COPY hermes-memory-reconcile.sh /opt/sellable/share/hermes-memory-reconcile.sh
86
88
  COPY fly-admin-image/rootfs/ /
@@ -89,6 +91,15 @@ RUN node --input-type=module -e \
89
91
  'import { installHermesAgentBridge, HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT } from "/usr/local/lib/sellable-agent/hermes-bridge.mjs"; installHermesAgentBridge({ sourceRoot: "/opt/hermes", contract: HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT });' \
90
92
  && node --input-type=module -e \
91
93
  'await import("/usr/local/lib/sellable-agent/fly-runtime-identity.mjs");' \
94
+ && install -d -m 0755 /usr/local/share/sellable-agent \
95
+ && printf '%s\n' \
96
+ '#!/usr/bin/env node' \
97
+ 'import("file:///usr/local/lib/sellable-agent/fly-soul-bridge.mjs").then(({ runSoulBridgeCli }) => runSoulBridgeCli());' \
98
+ > /usr/local/bin/sellable-soul-bridge \
99
+ && chmod 0555 /usr/local/bin/sellable-soul-bridge \
100
+ && sha256sum /usr/local/lib/sellable-agent/fly-soul-bridge.mjs \
101
+ > /usr/local/share/sellable-agent/soul-bridge-source.sha256 \
102
+ && chmod 0444 /usr/local/share/sellable-agent/soul-bridge-source.sha256 \
92
103
  && chmod 0755 /etc \
93
104
  && chmod 0555 \
94
105
  /usr/local/lib/sellable-agent/admin-runtime/admin-runtime.mjs \
@@ -96,6 +107,8 @@ RUN node --input-type=module -e \
96
107
  /usr/local/lib/sellable-agent/fly-runtime-identity.mjs \
97
108
  /usr/local/lib/sellable-agent/hermes-bridge.mjs \
98
109
  /usr/local/lib/sellable-agent/hermes-memory-snapshot.mjs \
110
+ /usr/local/lib/sellable-agent/fly-soul-bridge.mjs \
111
+ /usr/local/lib/sellable-agent/soul-artifact-validator.mjs \
99
112
  /opt/sellable/bin/hermes-memory-dirty \
100
113
  /etc/s6-overlay/s6-rc.d/admin-runtime/run \
101
114
  && chmod 0444 \
@@ -48,7 +48,9 @@ import {
48
48
  import { expectedRuntimeGates } from "../admin-artifact/node_modules/@sellable/install/lib/sellable-agent/runtime-helper.mjs";
49
49
  import sodium from "../admin-artifact/node_modules/libsodium-wrappers/dist/modules-esm/libsodium-wrappers.mjs";
50
50
  import {
51
+ claimWithSoulObservation,
51
52
  createFlyCustomerControlClient,
53
+ createSoulClaimObservationTracker,
52
54
  dropFlyRuntimePrivileges,
53
55
  exchangeFlyAdminIdentity,
54
56
  flyRuntimeDigest,
@@ -59,6 +61,12 @@ import {
59
61
  renewFlyAdminControlSession,
60
62
  signedWorkerAuthorization,
61
63
  } from "../fly-runtime-identity.mjs";
64
+ import {
65
+ executeExternalSoulBridgeOperation,
66
+ observeProfileSoul,
67
+ prepareSoulLockRoot,
68
+ withProfileSoulLock,
69
+ } from "../fly-soul-bridge.mjs";
62
70
  import {
63
71
  HERMES_SNAPSHOT_HOOKS,
64
72
  HERMES_SNAPSHOT_HOOK_ALLOWLIST_BYTES,
@@ -588,88 +596,167 @@ async function materializeAdminProfile({
588
596
  const stageRoot = join(PROFILES_ROOT, ".staging", profile);
589
597
  const backupRoot = join(PROFILES_ROOT, ".backup", profile);
590
598
  privateDirectory(PROFILES_ROOT);
599
+ const readAuthoritativeSoul = () => {
600
+ const activeSoulPath = join(profileHome, "SOUL.md");
601
+ if (!existsSync(activeSoulPath)) return soul;
602
+ const activeSoul = lstatSync(activeSoulPath);
603
+ if (activeSoul.isSymbolicLink() || !activeSoul.isFile()) {
604
+ throw new Error("admin_runtime_live_soul_rejected");
605
+ }
606
+ const bytes = readFileSync(activeSoulPath);
607
+ return {
608
+ bytes,
609
+ sha256: sha256(bytes),
610
+ byteLength: bytes.byteLength,
611
+ };
612
+ };
591
613
  const layout = adminHomeLayout(profileHome);
592
614
  // stageAdminProfile canonicalizes these two paths before staging, so the
593
615
  // Admin home working copies must exist before the factory runs.
594
616
  deliverAdminSettings(settingsRoot, layout);
595
617
  const serverProofs = {};
596
- const stage = await stageAdminProfile({
597
- mode: slack ? "direct_soul_native_slack" : "direct_soul",
598
- lifecycleAuthorized: slack ? true : undefined,
599
- ephemeralProductConfig: Boolean(slack),
600
- ephemeralSlackSecrets: Boolean(slack),
601
- profile,
602
- profilesRoot: PROFILES_ROOT,
603
- stageRoot,
604
- finalProfileRoot: profileHome,
605
- rootAuthPath,
606
- canonicalAdminConfigPath: layout.configPath,
607
- canonicalAdminStatePath: layout.statePath,
608
- productConfigPath,
609
- model: "gpt-5.6-sol",
610
- reasoningEffort: "high",
611
- soul,
612
- ...(slack
613
- ? {
614
- slackSecretFile: slack.secretFile,
615
- teamId: slack.teamId,
616
- primaryChannel: slack.primaryChannel,
617
- secondaryReadChannelIds: slack.secondaryReadChannelIds,
618
- allowedSlackUserIds: slack.allowedSlackUserIds,
619
- slackApp: slack.slackApp,
618
+ const stageWithAuthority = async (stagedAuthority) => {
619
+ const stage = await stageAdminProfile({
620
+ mode: slack ? "direct_soul_native_slack" : "direct_soul",
621
+ lifecycleAuthorized: slack ? true : undefined,
622
+ ephemeralProductConfig: Boolean(slack),
623
+ ephemeralSlackSecrets: Boolean(slack),
624
+ profile,
625
+ profilesRoot: PROFILES_ROOT,
626
+ stageRoot,
627
+ finalProfileRoot: profileHome,
628
+ rootAuthPath,
629
+ canonicalAdminConfigPath: layout.configPath,
630
+ canonicalAdminStatePath: layout.statePath,
631
+ productConfigPath,
632
+ model: "gpt-5.6-sol",
633
+ reasoningEffort: "high",
634
+ soul: stagedAuthority,
635
+ ...(slack
636
+ ? {
637
+ slackSecretFile: slack.secretFile,
638
+ teamId: slack.teamId,
639
+ primaryChannel: slack.primaryChannel,
640
+ secondaryReadChannelIds: slack.secondaryReadChannelIds,
641
+ allowedSlackUserIds: slack.allowedSlackUserIds,
642
+ slackApp: slack.slackApp,
643
+ }
644
+ : {}),
645
+ skillsRoot: join(ADMIN_INSTALL_ASSET_ROOT, "mcp", "skills"),
646
+ installRoot: layout.adminRoot,
647
+ adminHome: layout.adminHome,
648
+ installAdmin: async () =>
649
+ profilePackageReceipt(profileContract, "sellable-admin", "mcp", layout),
650
+ installProduct: async () =>
651
+ profilePackageReceipt(
652
+ profileContract,
653
+ "sellable",
654
+ "mcp",
655
+ layout,
656
+ agentServiceEnv
657
+ ),
658
+ verifyMcp: async ({ kind, profileRoot, config }) => {
659
+ const alias = kind === "admin" ? "sellable-admin" : "sellable";
660
+ const server = config.mcp_servers[alias];
661
+ const proof = await inspectMcp({
662
+ alias,
663
+ command: server.command,
664
+ args: server.args,
665
+ env: {
666
+ HOME: profileRoot,
667
+ HERMES_HOME: profileRoot,
668
+ ...adminToolEnv(layout),
669
+ ...agentServiceEnv,
670
+ SELLABLE_CONFIG_PATH: productConfigPath,
671
+ ...(server.env ?? {}),
672
+ },
673
+ packageProof: profileContract.packages[alias].mcp,
674
+ });
675
+ if (
676
+ JSON.stringify(proof.tools) !==
677
+ JSON.stringify(profileContract.tools[alias])
678
+ ) {
679
+ throw new Error("admin_runtime_tool_inventory_mismatch");
620
680
  }
621
- : {}),
622
- skillsRoot: join(ADMIN_INSTALL_ASSET_ROOT, "mcp", "skills"),
623
- installRoot: layout.adminRoot,
624
- adminHome: layout.adminHome,
625
- installAdmin: async () =>
626
- profilePackageReceipt(profileContract, "sellable-admin", "mcp", layout),
627
- installProduct: async () =>
628
- profilePackageReceipt(
629
- profileContract,
630
- "sellable",
631
- "mcp",
632
- layout,
633
- agentServiceEnv
634
- ),
635
- verifyMcp: async ({ kind, profileRoot, config }) => {
636
- const alias = kind === "admin" ? "sellable-admin" : "sellable";
637
- const server = config.mcp_servers[alias];
638
- const proof = await inspectMcp({
639
- alias,
640
- command: server.command,
641
- args: server.args,
642
- env: {
643
- HOME: profileRoot,
644
- HERMES_HOME: profileRoot,
645
- ...adminToolEnv(layout),
646
- ...agentServiceEnv,
647
- SELLABLE_CONFIG_PATH: productConfigPath,
648
- ...(server.env ?? {}),
649
- },
650
- packageProof: profileContract.packages[alias].mcp,
651
- });
652
- if (
653
- JSON.stringify(proof.tools) !==
654
- JSON.stringify(profileContract.tools[alias])
655
- ) {
656
- throw new Error("admin_runtime_tool_inventory_mismatch");
657
- }
658
- serverProofs[alias] = proof;
659
- return { ok: true, ...proof };
660
- },
661
- });
662
- mergeHermesState(profileHome, stageRoot);
663
- installAdminHermesMemoryHook(stageRoot);
664
- stageAdminRuntimeTree(stageRoot);
665
- const promotion = await promoteAdminProfileAtomically({
666
- profile,
667
- currentRoot: profileHome,
668
- stagedRoot: stageRoot,
669
- backupRoot,
681
+ serverProofs[alias] = proof;
682
+ return { ok: true, ...proof };
683
+ },
684
+ });
685
+ mergeHermesState(profileHome, stageRoot);
686
+ installAdminHermesMemoryHook(stageRoot);
687
+ stageAdminRuntimeTree(stageRoot);
688
+ return stage;
689
+ };
690
+
691
+ let stagedAuthority = withProfileSoulLock(profile, readAuthoritativeSoul, {
692
+ dataRoot: DATA_ROOT,
670
693
  });
671
- // The installer bakes absolute paths into every generated wrapper, so the
672
- // bin/ tree is generated once the promoted Admin home is at its final path.
694
+ let stage = null;
695
+ let promoted = null;
696
+ for (let attempt = 0; attempt < 2 && !promoted; attempt += 1) {
697
+ stage = await stageWithAuthority(stagedAuthority);
698
+ const committed = await withProfileSoulLock(
699
+ profile,
700
+ async () => {
701
+ const finalAuthority = readAuthoritativeSoul();
702
+ if (
703
+ finalAuthority.sha256 !== stagedAuthority.sha256 ||
704
+ !finalAuthority.bytes.equals(stagedAuthority.bytes)
705
+ ) {
706
+ rmSync(stageRoot, { recursive: true, force: true });
707
+ return { status: "RESTAGE", stagedAuthority: finalAuthority };
708
+ }
709
+ const stagedSoulPath = join(stageRoot, "SOUL.md");
710
+ const stagedSoul = lstatSync(stagedSoulPath);
711
+ const stagedSoulBytes = readFileSync(stagedSoulPath);
712
+ if (
713
+ stagedSoul.isSymbolicLink() ||
714
+ !stagedSoul.isFile() ||
715
+ !stagedSoulBytes.equals(finalAuthority.bytes)
716
+ ) {
717
+ throw new Error("admin_runtime_staged_soul_rejected");
718
+ }
719
+ const promotion = await promoteAdminProfileAtomically({
720
+ profile,
721
+ currentRoot: profileHome,
722
+ stagedRoot: stageRoot,
723
+ backupRoot,
724
+ });
725
+ const finalSoulPath = join(profileHome, "SOUL.md");
726
+ const finalSoul = lstatSync(finalSoulPath);
727
+ const finalSoulBytes = readFileSync(finalSoulPath);
728
+ if (
729
+ finalSoul.isSymbolicLink() ||
730
+ !finalSoul.isFile() ||
731
+ finalSoul.uid !== (process.getuid?.() ?? finalSoul.uid) ||
732
+ finalSoul.gid !== (process.getgid?.() ?? finalSoul.gid) ||
733
+ !finalSoulBytes.equals(finalAuthority.bytes)
734
+ ) {
735
+ throw new Error("admin_runtime_live_soul_readback_rejected");
736
+ }
737
+ chmodSync(finalSoulPath, 0o600);
738
+ if ((lstatSync(finalSoulPath).mode & 0o777) !== 0o600) {
739
+ throw new Error("admin_runtime_live_soul_mode_rejected");
740
+ }
741
+ return {
742
+ status: "PROMOTED",
743
+ promotion,
744
+ observedSoulRawSha256: sha256(finalSoulBytes),
745
+ };
746
+ },
747
+ { dataRoot: DATA_ROOT }
748
+ );
749
+ if (committed.status === "PROMOTED") {
750
+ promoted = committed;
751
+ } else {
752
+ stagedAuthority = committed.stagedAuthority;
753
+ }
754
+ }
755
+ if (!promoted || !stage) {
756
+ throw new Error("admin_runtime_live_soul_retry_required");
757
+ }
758
+ // The installer bakes absolute paths into every generated wrapper, so bin/
759
+ // is generated once the promoted Admin home is at its final path.
673
760
  const adminHome = installAdminHome({ settingsRoot, layout });
674
761
  const memoryCron = await ensureHermesSnapshotCron({
675
762
  profileId: profile,
@@ -680,10 +767,11 @@ async function materializeAdminProfile({
680
767
  if (!memoryCron.ok) throw new Error(memoryCron.code);
681
768
  return {
682
769
  stage,
683
- promotion,
770
+ promotion: promoted.promotion,
684
771
  profile,
685
772
  profileHome,
686
773
  adminHome,
774
+ observedSoulRawSha256: promoted.observedSoulRawSha256,
687
775
  servers: [serverProofs.sellable, serverProofs["sellable-admin"]].map(
688
776
  ({ tools: _tools, ...proof }) => proof
689
777
  ),
@@ -1716,6 +1804,9 @@ function completionReceipt({
1716
1804
  const routeGenerationDigest = flyRuntimeDigest(
1717
1805
  claim.revision?.manifest?.slack ?? null
1718
1806
  );
1807
+ const observedProfileDigest = sha256(
1808
+ `${operation.profileContract.digest}\n${profile.observedSoulRawSha256}`
1809
+ );
1719
1810
  const adminRuntimeProof = {
1720
1811
  schemaVersion: "sellable-agent-admin-runtime-proof/v1",
1721
1812
  authenticatedKind: "ADMIN",
@@ -1726,7 +1817,7 @@ function completionReceipt({
1726
1817
  adminImageDigest: claim.adminRuntimeGeneration.adminImageDigest,
1727
1818
  profile: {
1728
1819
  factory: "stageAdminProfile",
1729
- digest: operation.profileContract.digest,
1820
+ digest: observedProfileDigest,
1730
1821
  aliases: ["sellable", "sellable-admin"],
1731
1822
  routeGenerationDigest,
1732
1823
  },
@@ -1748,7 +1839,7 @@ function completionReceipt({
1748
1839
  const runtimeHashes = {
1749
1840
  package: sha256(JSON.stringify(packageSpecs())),
1750
1841
  config: operation.profileContract.digest,
1751
- content: operation.profileContract.digest,
1842
+ content: observedProfileDigest,
1752
1843
  process: sha256(
1753
1844
  JSON.stringify({ servers: profile.servers, modelAuth, runtimeGates })
1754
1845
  ),
@@ -1780,6 +1871,7 @@ function completionReceipt({
1780
1871
  errorClass: "NONE",
1781
1872
  auditIds: [
1782
1873
  `fly-admin-${claim.operation.id}`,
1874
+ `soul-raw-${profile.observedSoulRawSha256}`,
1783
1875
  `hermes-memory-${
1784
1876
  memorySnapshot.ok ? memorySnapshot.status : memorySnapshot.code
1785
1877
  }`.toLowerCase(),
@@ -1943,7 +2035,12 @@ export async function runAdminCanary() {
1943
2035
  throw new Error("admin_canary_recovery_secret_file_rejected");
1944
2036
  }
1945
2037
  if (process.getuid?.() === 0) {
1946
- dropFlyRuntimePrivileges({ user: "hermes", group: "hermes" });
2038
+ dropFlyRuntimePrivileges({
2039
+ user: "hermes",
2040
+ group: "hermes",
2041
+ beforeSetuid: ({ gid }) =>
2042
+ prepareSoulLockRoot({ dataRoot: DATA_ROOT, gid }),
2043
+ });
1947
2044
  }
1948
2045
  const runtimeUid = process.getuid?.() ?? 0;
1949
2046
  const runtimeGid = process.getgid?.() ?? 0;
@@ -2207,6 +2304,13 @@ export async function runAdminCanary() {
2207
2304
  },
2208
2305
  agentServiceEnv: agentServiceEnvironment,
2209
2306
  });
2307
+ const soulProof = observeProfileSoul(profile.profile, {
2308
+ dataRoot: DATA_ROOT,
2309
+ });
2310
+ if (soulProof.ok !== true) {
2311
+ throw new Error("admin_runtime_live_soul_loader_proof_rejected");
2312
+ }
2313
+ profile.observedSoulRawSha256 = soulProof.rawFileSha256;
2210
2314
  const modelAuth = proveAdminModelAuth(profile);
2211
2315
  const attachmentHandoff = proveAdminAttachmentHandoff(profile);
2212
2316
  const observedSettings = settingsProof(
@@ -2318,10 +2422,12 @@ async function runAdminProduction() {
2318
2422
  dropFlyRuntimePrivileges({
2319
2423
  user: "hermes",
2320
2424
  group: "hermes",
2321
- beforeSetuid:
2322
- process.env.SELLABLE_AGENT_ALLOW_TEST_OIDC === "1"
2323
- ? undefined
2324
- : ({ gid }) => handoffFlyOidcSocket({ gid }),
2425
+ beforeSetuid: ({ gid }) => {
2426
+ prepareSoulLockRoot({ dataRoot: DATA_ROOT, gid });
2427
+ if (process.env.SELLABLE_AGENT_ALLOW_TEST_OIDC !== "1") {
2428
+ handoffFlyOidcSocket({ gid });
2429
+ }
2430
+ },
2325
2431
  });
2326
2432
  } else if ((process.getuid?.() ?? 0) < 1 || (process.getgid?.() ?? 0) < 1) {
2327
2433
  throw new Error("admin_runtime_privilege_rejected");
@@ -2387,12 +2493,25 @@ async function runAdminProduction() {
2387
2493
  process.once("SIGINT", stop);
2388
2494
  process.once("SIGTERM", stop);
2389
2495
  let operationHeartbeat = null;
2496
+ let soulClaimTracker = null;
2390
2497
  let failures = 0;
2391
2498
  while (!controller.signal.aborted) {
2392
2499
  try {
2393
2500
  let claim;
2394
2501
  try {
2395
- claim = await client.claim(60);
2502
+ claim =
2503
+ activeLaunch?.profile?.profile && soulClaimTracker
2504
+ ? await claimWithSoulObservation({
2505
+ client,
2506
+ tracker: soulClaimTracker,
2507
+ readSoul: (request) =>
2508
+ executeExternalSoulBridgeOperation(request, {
2509
+ dataRoot: DATA_ROOT,
2510
+ callerProfileId: activeLaunch.profile.profile,
2511
+ }),
2512
+ leaseSeconds: 60,
2513
+ })
2514
+ : await client.claim(60);
2396
2515
  } catch (error) {
2397
2516
  if (!isFlyCustomerEnrollmentRenewalError(error)) throw error;
2398
2517
  const renewed = await renewFlyAdminControlSession({
@@ -2567,6 +2686,13 @@ async function runAdminProduction() {
2567
2686
  slack: nativeSlackProfileInput(claim, installed),
2568
2687
  agentServiceEnv: adminAgentServiceEnvironment(claim),
2569
2688
  });
2689
+ const soulProof = observeProfileSoul(profile.profile, {
2690
+ dataRoot: DATA_ROOT,
2691
+ });
2692
+ if (soulProof.ok !== true) {
2693
+ throw new Error("admin_runtime_live_soul_loader_proof_rejected");
2694
+ }
2695
+ profile.observedSoulRawSha256 = soulProof.rawFileSha256;
2570
2696
  const modelAuth = proveAdminModelAuth(profile);
2571
2697
  const memorySnapshot = await flushHermesSnapshot({
2572
2698
  client,
@@ -2621,8 +2747,13 @@ async function runAdminProduction() {
2621
2747
  if (candidateLaunch && candidateGateway) {
2622
2748
  activeLaunch = candidateLaunch;
2623
2749
  activeGateway = candidateGateway;
2750
+ soulClaimTracker = createSoulClaimObservationTracker({
2751
+ runtimeId: RUNTIME_ID,
2752
+ profileId: candidateLaunch.profile.profile,
2753
+ });
2624
2754
  candidateGateway = null;
2625
2755
  } else {
2756
+ soulClaimTracker = null;
2626
2757
  clearRuntimeSecrets();
2627
2758
  }
2628
2759
  } finally {
@@ -3,9 +3,9 @@ FROM ${HERMES_IMAGE}
3
3
 
4
4
  USER root
5
5
 
6
- ARG MCP_PACKAGE=@sellable/mcp@0.1.805
7
- ARG MCP_INTEGRITY=sha512-ULMGwWalb14SaMgxLeeiOWjavXpnONhdD8kAkvp7rJFdiL7s30VwXX+1j/aNwijiWXqqNQ1wcsXUPqk4eWpslg==
8
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.484
6
+ ARG MCP_PACKAGE=@sellable/mcp@0.1.806-wip.phase169.20260804195555
7
+ ARG MCP_INTEGRITY=sha512-g0FHlsG8TenNpdxBoJWsuveCD3uTUrBPR9Xk1WfEliOmy2sbLNtCCe/nCJMcj/hJdfKV0aOisyMbAx7xkwr9Bg==
8
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.485-wip.phase169.20260804195555
9
9
  ARG TUS_JS_CLIENT_VERSION=4.3.1
10
10
  ARG HERMES_VERSION=0.20.0
11
11
  ARG MCP_TRUST_ROOT_B64
@@ -56,7 +56,7 @@ RUN test -n "${MCP_TRUST_ROOT_B64}" \
56
56
  /etc/cont-init.d/02-reconcile-profiles \
57
57
  && npm cache clean --force
58
58
 
59
- COPY mcp-context-proxy.mjs fly-customer-model.mjs fly-customer-worker.mjs profile-materializer.mjs hermes-bridge.mjs hermes-memory-snapshot.mjs fly-runtime-identity.mjs \
59
+ COPY mcp-context-proxy.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 soul-artifact-validator.mjs \
60
60
  /usr/local/lib/sellable-agent/
61
61
  COPY hermes-memory-dirty.mjs /opt/sellable/bin/hermes-memory-dirty
62
62
  COPY hermes-memory-reconcile.sh /opt/sellable/share/hermes-memory-reconcile.sh
@@ -68,6 +68,14 @@ RUN node --input-type=module -e \
68
68
  'import { installHermesAgentBridge, HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT } from "/usr/local/lib/sellable-agent/hermes-bridge.mjs"; installHermesAgentBridge({ sourceRoot: "/opt/hermes", contract: HERMES_AGENT_BRIDGE_DEDICATED_V020_CONTRACT });' \
69
69
  && node --input-type=module -e \
70
70
  'await import("/usr/local/lib/sellable-agent/fly-runtime-identity.mjs");' \
71
+ && printf '%s\n' \
72
+ '#!/usr/bin/env node' \
73
+ 'import("file:///usr/local/lib/sellable-agent/fly-soul-bridge.mjs").then(({ runSoulBridgeCli }) => runSoulBridgeCli());' \
74
+ > /usr/local/bin/sellable-soul-bridge \
75
+ && chmod 0555 /usr/local/bin/sellable-soul-bridge \
76
+ && sha256sum /usr/local/lib/sellable-agent/fly-soul-bridge.mjs \
77
+ > /usr/local/share/sellable-agent/soul-bridge-source.sha256 \
78
+ && chmod 0444 /usr/local/share/sellable-agent/soul-bridge-source.sha256 \
71
79
  && chmod 0555 /usr/local/lib/sellable-agent/*.mjs \
72
80
  && chmod 0555 /opt/sellable/bin/hermes-memory-dirty \
73
81
  && chmod 0444 /opt/sellable/share/hermes-memory-reconcile.sh \