@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.
- package/container/Dockerfile +4 -4
- package/container/README.md +2 -2
- package/lib/sellable-agent/fly-admin-image/Dockerfile +13 -0
- package/lib/sellable-agent/fly-admin-image/admin-runtime.mjs +215 -84
- package/lib/sellable-agent/fly-customer-image/Dockerfile +12 -4
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +46 -16
- package/lib/sellable-agent/fly-runtime-identity.mjs +152 -7
- package/lib/sellable-agent/fly-soul-bridge.mjs +1442 -0
- package/lib/sellable-agent/hermes-bridge.mjs +263 -2
- package/lib/sellable-agent/hermes-memory-snapshot.mjs +236 -21
- package/lib/sellable-agent/host-bootstrap.mjs +1 -1
- package/lib/sellable-agent/host-worker.mjs +9 -2
- package/lib/sellable-agent/profile-materializer.mjs +118 -43
- package/lib/sellable-agent/provisioning-adapter.mjs +4 -4
- package/lib/sellable-agent/runtime-helper.mjs +7 -0
- package/lib/sellable-agent/runtime-reconciler.mjs +37 -15
- package/lib/sellable-agent/soul-artifact-validator.mjs +117 -0
- 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.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.
|
|
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.
|
|
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.
|
|
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
|
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.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.
|
|
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
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
|
|
672
|
-
|
|
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:
|
|
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:
|
|
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({
|
|
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
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
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 =
|
|
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.
|
|
7
|
-
ARG MCP_INTEGRITY=sha512-
|
|
8
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
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 \
|