@sellable/install 0.1.641 → 0.1.643
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 +1 -1
- package/lib/sellable-agent/default-profile-bundle.mjs +318 -0
- package/lib/sellable-agent/default-profile-bundles/customer/manifest.fragment.json +8 -0
- package/lib/sellable-agent/default-profile-bundles/fixtures/v1/manifest.fragment.json +26 -0
- package/lib/sellable-agent/default-profile-bundles/fixtures/v2/manifest.fragment.json +34 -0
- package/lib/sellable-agent/default-profile-bundles/shared/manifest.fragment.json +8 -0
- package/lib/sellable-agent/default-profile-reconciler.mjs +508 -0
- package/lib/sellable-agent/fly-admin-image/Dockerfile +64 -4
- package/lib/sellable-agent/fly-admin-image/admin-runtime.mjs +168 -12
- package/lib/sellable-agent/fly-cron-proof-exec.mjs +218 -0
- package/lib/sellable-agent/fly-customer-image/Dockerfile +29 -4
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +131 -3
- package/lib/sellable-agent/fly-customer-worker.mjs +20 -2
- package/lib/sellable-agent/fly-runtime-identity.mjs +41 -2
- package/lib/sellable-agent/fly-skills-bridge.mjs +63 -7
- package/lib/sellable-agent/host-bootstrap.mjs +1 -1
- package/lib/sellable-agent/host-worker.mjs +1 -1
- package/lib/sellable-agent/profile-materializer.mjs +1 -1
- package/lib/sellable-agent/provisioning-adapter.mjs +1 -1
- package/package.json +1 -1
|
@@ -43,6 +43,12 @@ import {
|
|
|
43
43
|
observeProfileSoul,
|
|
44
44
|
prepareSoulLockRoot,
|
|
45
45
|
} from "../fly-soul-bridge.mjs";
|
|
46
|
+
import { proveDefaultProfileNativeSkills } from "../fly-skills-bridge.mjs";
|
|
47
|
+
import {
|
|
48
|
+
bindDefaultProfileProbeCron,
|
|
49
|
+
buildDefaultProfileProjection,
|
|
50
|
+
reconcileDefaultProfileBundle,
|
|
51
|
+
} from "../default-profile-reconciler.mjs";
|
|
46
52
|
import {
|
|
47
53
|
ensureHermesSnapshotCron,
|
|
48
54
|
markHermesSnapshotDirty,
|
|
@@ -99,13 +105,67 @@ const BOOT_SESSION_ID = randomUUID();
|
|
|
99
105
|
const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.0";
|
|
100
106
|
const INSTALLER_PACKAGE =
|
|
101
107
|
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ??
|
|
102
|
-
"@sellable/install@0.1.
|
|
108
|
+
"@sellable/install@0.1.643";
|
|
103
109
|
const MCP_PACKAGE =
|
|
104
110
|
process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.879";
|
|
111
|
+
const DEFAULT_PROFILE_BUNDLE_ROOT =
|
|
112
|
+
"/usr/local/share/sellable-agent/default-profile-bundle";
|
|
113
|
+
const DEFAULT_PROFILE_CRON_PYTHON = [
|
|
114
|
+
"import json,sys",
|
|
115
|
+
"from cron.jobs import create_job, update_job",
|
|
116
|
+
"value=json.loads(sys.argv[1])",
|
|
117
|
+
"job_id=value.pop('jobId', None)",
|
|
118
|
+
"enabled=value.pop('enabled')",
|
|
119
|
+
"result=update_job(job_id, {**value, 'enabled': enabled}) if job_id else create_job(**value, enabled=enabled)",
|
|
120
|
+
"print(json.dumps({'id': result.get('id') if result else None}))",
|
|
121
|
+
].join("\n");
|
|
105
122
|
|
|
106
123
|
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
107
124
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
108
125
|
|
|
126
|
+
function customerDefaultProfileCronAdapter(profileId, profileRoot) {
|
|
127
|
+
const mutate = async ({ desired, spec, existing }) => {
|
|
128
|
+
const child = await run(
|
|
129
|
+
HERMES_PYTHON,
|
|
130
|
+
[
|
|
131
|
+
"-c",
|
|
132
|
+
DEFAULT_PROFILE_CRON_PYTHON,
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
name: desired.name,
|
|
135
|
+
schedule: spec.schedule,
|
|
136
|
+
script: desired.script,
|
|
137
|
+
no_agent: true,
|
|
138
|
+
deliver: "local",
|
|
139
|
+
enabled: true,
|
|
140
|
+
...(existing?.id ? { jobId: existing.id } : {}),
|
|
141
|
+
}),
|
|
142
|
+
],
|
|
143
|
+
{
|
|
144
|
+
env: {
|
|
145
|
+
...process.env,
|
|
146
|
+
HOME: DATA_ROOT,
|
|
147
|
+
HERMES_HOME: profileRoot,
|
|
148
|
+
HERMES_PROFILE: profileId,
|
|
149
|
+
NO_COLOR: "1",
|
|
150
|
+
},
|
|
151
|
+
timeoutMs: 120_000,
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
return {
|
|
155
|
+
...child,
|
|
156
|
+
exitCode: child.status,
|
|
157
|
+
signal: null,
|
|
158
|
+
outputLimitExceeded: false,
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
return {
|
|
162
|
+
create: (input) => mutate(input),
|
|
163
|
+
update: (input) => mutate(input),
|
|
164
|
+
bind: (input) =>
|
|
165
|
+
bindDefaultProfileProbeCron({ ...input, bootId: BOOT_SESSION_ID }),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
109
169
|
function atomicFile(path, bytes, mode = 0o600) {
|
|
110
170
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
111
171
|
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
@@ -498,6 +558,12 @@ export function customerServingObservation(activeRuntime, exchange) {
|
|
|
498
558
|
},
|
|
499
559
|
productMcp: authority.productMcp,
|
|
500
560
|
adminMcp: null,
|
|
561
|
+
...(authority.bundleVersion === undefined
|
|
562
|
+
? {}
|
|
563
|
+
: {
|
|
564
|
+
bundleVersion: authority.bundleVersion,
|
|
565
|
+
bundleDigest: authority.bundleDigest,
|
|
566
|
+
}),
|
|
501
567
|
nativeGateway: true,
|
|
502
568
|
customerRouting: true,
|
|
503
569
|
});
|
|
@@ -533,6 +599,11 @@ function startOperationHeartbeat({ claim, exchange, client }) {
|
|
|
533
599
|
assertHealthy() {
|
|
534
600
|
if (failure) throw failure;
|
|
535
601
|
},
|
|
602
|
+
async reattest() {
|
|
603
|
+
beat();
|
|
604
|
+
await inFlight;
|
|
605
|
+
if (failure) throw failure;
|
|
606
|
+
},
|
|
536
607
|
async stop() {
|
|
537
608
|
stopped = true;
|
|
538
609
|
clearInterval(timer);
|
|
@@ -2631,6 +2702,8 @@ function completionReceipt({
|
|
|
2631
2702
|
proof,
|
|
2632
2703
|
memorySnapshot,
|
|
2633
2704
|
observedSoulRawSha256,
|
|
2705
|
+
activeProjection,
|
|
2706
|
+
nativeDefaultProfileProof,
|
|
2634
2707
|
}) {
|
|
2635
2708
|
const identity = operationIdentity(claim, exchange);
|
|
2636
2709
|
const observedLifecycle =
|
|
@@ -2651,6 +2724,12 @@ function completionReceipt({
|
|
|
2651
2724
|
secretDisposition: "INSTALLED",
|
|
2652
2725
|
observedLifecycle,
|
|
2653
2726
|
observedRevisionId: desired.revisionId,
|
|
2727
|
+
...(activeProjection
|
|
2728
|
+
? {
|
|
2729
|
+
bundleVersion: activeProjection.bundleVersion,
|
|
2730
|
+
bundleDigest: activeProjection.bundleDigest,
|
|
2731
|
+
}
|
|
2732
|
+
: {}),
|
|
2654
2733
|
signerGeneration: exchange.keyGeneration,
|
|
2655
2734
|
signedDigest: flyRuntimeDigest({
|
|
2656
2735
|
...identity,
|
|
@@ -2680,6 +2759,11 @@ function completionReceipt({
|
|
|
2680
2759
|
},
|
|
2681
2760
|
{ code: "NO_BOOT_INSTALL", status: "TRUE" },
|
|
2682
2761
|
{ code: "DEDICATED_SLACK_APP", status: "TRUE" },
|
|
2762
|
+
{
|
|
2763
|
+
code: "DEFAULT_PROFILE_NATIVE_PROOF",
|
|
2764
|
+
status:
|
|
2765
|
+
nativeDefaultProfileProof.status === "PROVEN" ? "TRUE" : "UNKNOWN",
|
|
2766
|
+
},
|
|
2683
2767
|
],
|
|
2684
2768
|
errorClass: "NONE",
|
|
2685
2769
|
auditIds: [
|
|
@@ -2697,9 +2781,10 @@ async function flushHermesSnapshot({
|
|
|
2697
2781
|
profileId,
|
|
2698
2782
|
profileRoot,
|
|
2699
2783
|
operationIdentity,
|
|
2784
|
+
forceDirty = false,
|
|
2700
2785
|
}) {
|
|
2701
2786
|
try {
|
|
2702
|
-
markHermesSnapshotDirty(profileRoot);
|
|
2787
|
+
if (forceDirty) markHermesSnapshotDirty(profileRoot);
|
|
2703
2788
|
return await reconcileHermesSnapshot({
|
|
2704
2789
|
profileId,
|
|
2705
2790
|
profileRoot,
|
|
@@ -2773,7 +2858,13 @@ function runtimeModelHealthReporter({ identity, exchange, runtimeConfig }) {
|
|
|
2773
2858
|
});
|
|
2774
2859
|
}
|
|
2775
2860
|
|
|
2776
|
-
async function processOperation({
|
|
2861
|
+
async function processOperation({
|
|
2862
|
+
claim,
|
|
2863
|
+
exchange,
|
|
2864
|
+
client,
|
|
2865
|
+
identity,
|
|
2866
|
+
reattestAuthority,
|
|
2867
|
+
}) {
|
|
2777
2868
|
const trustRootPem = readFileSync(TRUST_ROOT, "utf8");
|
|
2778
2869
|
const { desired, toolInclude } = compileDesired(claim, trustRootPem);
|
|
2779
2870
|
mkdirSync(PROFILES_ROOT, { recursive: true, mode: 0o700 });
|
|
@@ -2812,6 +2903,28 @@ async function processOperation({ claim, exchange, client, identity }) {
|
|
|
2812
2903
|
profileRoot,
|
|
2813
2904
|
});
|
|
2814
2905
|
if (!pristineProfile.ok) throw new Error(pristineProfile.code);
|
|
2906
|
+
const activeProjection = buildDefaultProfileProjection({
|
|
2907
|
+
kind: "CUSTOMER",
|
|
2908
|
+
claim,
|
|
2909
|
+
});
|
|
2910
|
+
const reconciledDefaultProfile = await reconcileDefaultProfileBundle({
|
|
2911
|
+
activeProjection,
|
|
2912
|
+
materializerReceipt: pristineProfile.manifest,
|
|
2913
|
+
profileRoot,
|
|
2914
|
+
bundleRoot: DEFAULT_PROFILE_BUNDLE_ROOT,
|
|
2915
|
+
dataRoot: DATA_ROOT,
|
|
2916
|
+
reattestAuthority: async () => {
|
|
2917
|
+
await reattestAuthority();
|
|
2918
|
+
return activeProjection;
|
|
2919
|
+
},
|
|
2920
|
+
cronAdapter: customerDefaultProfileCronAdapter(
|
|
2921
|
+
desired.profileId,
|
|
2922
|
+
profileRoot
|
|
2923
|
+
),
|
|
2924
|
+
});
|
|
2925
|
+
if (!reconciledDefaultProfile.ok)
|
|
2926
|
+
throw new Error(reconciledDefaultProfile.code);
|
|
2927
|
+
const defaultProfile = reconciledDefaultProfile;
|
|
2815
2928
|
installInternalAdminRuntimeSettings({ desired, profileRoot });
|
|
2816
2929
|
const runtimeConfig = writeRuntimeHermesConfig({
|
|
2817
2930
|
desired,
|
|
@@ -2835,6 +2948,10 @@ async function processOperation({ claim, exchange, client, identity }) {
|
|
|
2835
2948
|
hermesCli: HERMES,
|
|
2836
2949
|
});
|
|
2837
2950
|
if (!memoryCron.ok) throw new Error(memoryCron.code);
|
|
2951
|
+
const materializerChanged =
|
|
2952
|
+
materialized.status !== "REUSED" || memoryCron.status !== "REUSED";
|
|
2953
|
+
if (materializerChanged || defaultProfile.changed)
|
|
2954
|
+
markHermesSnapshotDirty(profileRoot);
|
|
2838
2955
|
const memorySnapshot = await flushHermesSnapshot({
|
|
2839
2956
|
client,
|
|
2840
2957
|
profileId: desired.profileId,
|
|
@@ -2851,6 +2968,13 @@ async function processOperation({ claim, exchange, client, identity }) {
|
|
|
2851
2968
|
secrets,
|
|
2852
2969
|
profileDigest: pristineProfile.manifest.profileDigest,
|
|
2853
2970
|
});
|
|
2971
|
+
const nativeDefaultProfileProof = proof.gatewayReadback?.running
|
|
2972
|
+
? proveDefaultProfileNativeSkills({
|
|
2973
|
+
actions: defaultProfile.actions,
|
|
2974
|
+
profileId: desired.profileId,
|
|
2975
|
+
dataRoot: DATA_ROOT,
|
|
2976
|
+
})
|
|
2977
|
+
: { status: "EMPTY", digest: sha256("[]") };
|
|
2854
2978
|
const receipt = completionReceipt({
|
|
2855
2979
|
claim,
|
|
2856
2980
|
exchange,
|
|
@@ -2858,6 +2982,8 @@ async function processOperation({ claim, exchange, client, identity }) {
|
|
|
2858
2982
|
proof,
|
|
2859
2983
|
memorySnapshot,
|
|
2860
2984
|
observedSoulRawSha256,
|
|
2985
|
+
activeProjection,
|
|
2986
|
+
nativeDefaultProfileProof,
|
|
2861
2987
|
});
|
|
2862
2988
|
const candidateRuntime = {
|
|
2863
2989
|
claim,
|
|
@@ -3133,6 +3259,7 @@ async function main() {
|
|
|
3133
3259
|
profileId: claim.operation.profileId,
|
|
3134
3260
|
profileRoot: join(PROFILES_ROOT, claim.operation.profileId),
|
|
3135
3261
|
operationIdentity: snapshotIdentity,
|
|
3262
|
+
forceDirty: true,
|
|
3136
3263
|
});
|
|
3137
3264
|
if (!backup.ok || backup.status !== "UPLOADED") {
|
|
3138
3265
|
throw new Error(
|
|
@@ -3176,6 +3303,7 @@ async function main() {
|
|
|
3176
3303
|
exchange,
|
|
3177
3304
|
client,
|
|
3178
3305
|
identity,
|
|
3306
|
+
reattestAuthority: () => operationHeartbeat.reattest(),
|
|
3179
3307
|
});
|
|
3180
3308
|
soulClaimTracker = createSoulClaimObservationTracker({
|
|
3181
3309
|
runtimeId: RUNTIME_ID,
|
|
@@ -17,6 +17,21 @@ import { parseFlyCustomerModelCredential } from "./fly-customer-model.mjs";
|
|
|
17
17
|
|
|
18
18
|
const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
19
19
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
20
|
+
|
|
21
|
+
function imageBundleIdentity(env) {
|
|
22
|
+
const rawVersion = env?.SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_VERSION;
|
|
23
|
+
const bundleDigest = env?.SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_DIGEST;
|
|
24
|
+
if (rawVersion === undefined && bundleDigest === undefined) return null;
|
|
25
|
+
const bundleVersion = Number(rawVersion);
|
|
26
|
+
if (
|
|
27
|
+
!Number.isSafeInteger(bundleVersion) ||
|
|
28
|
+
bundleVersion < 1 ||
|
|
29
|
+
!SHA256.test(bundleDigest ?? "")
|
|
30
|
+
) {
|
|
31
|
+
throw new Error("runtime_serving_observation_rejected");
|
|
32
|
+
}
|
|
33
|
+
return Object.freeze({ bundleVersion, bundleDigest });
|
|
34
|
+
}
|
|
20
35
|
function sha256(value) {
|
|
21
36
|
return createHash("sha256").update(value).digest("hex");
|
|
22
37
|
}
|
|
@@ -38,7 +53,8 @@ export function isOperationBoundBackupClaim(claim) {
|
|
|
38
53
|
}
|
|
39
54
|
|
|
40
55
|
// CUSTOMER and ADMIN runtimes share this one serving-observation builder.
|
|
41
|
-
export function buildRuntimeServingObservation(input = {}) {
|
|
56
|
+
export function buildRuntimeServingObservation(input = {}, env = process.env) {
|
|
57
|
+
const bundleIdentity = imageBundleIdentity(env);
|
|
42
58
|
const keys = [
|
|
43
59
|
"scope",
|
|
44
60
|
"kind",
|
|
@@ -60,6 +76,7 @@ export function buildRuntimeServingObservation(input = {}) {
|
|
|
60
76
|
"adminMcp",
|
|
61
77
|
"nativeGateway",
|
|
62
78
|
"customerRouting",
|
|
79
|
+
...(bundleIdentity ? ["bundleVersion", "bundleDigest"] : []),
|
|
63
80
|
];
|
|
64
81
|
const identity = (value) => typeof value === "string" && SAFE_ID.test(value);
|
|
65
82
|
const mcp = (value, name) =>
|
|
@@ -77,7 +94,7 @@ export function buildRuntimeServingObservation(input = {}) {
|
|
|
77
94
|
mcp(input.adminMcp, "sellable-admin-mcp") &&
|
|
78
95
|
input.customerRouting === false;
|
|
79
96
|
if (
|
|
80
|
-
!exactKeys(input, keys) ||
|
|
97
|
+
!exactKeys({ ...input, ...(bundleIdentity ?? {}) }, keys) ||
|
|
81
98
|
!["ASSIGNED", "CANDIDATE"].includes(input.scope) ||
|
|
82
99
|
!keys
|
|
83
100
|
.slice(2, 13)
|
|
@@ -106,6 +123,7 @@ export function buildRuntimeServingObservation(input = {}) {
|
|
|
106
123
|
return Object.freeze({
|
|
107
124
|
schemaVersion: "sellable-agent-serving-observation/v2",
|
|
108
125
|
...input,
|
|
126
|
+
...(bundleIdentity ?? {}),
|
|
109
127
|
});
|
|
110
128
|
}
|
|
111
129
|
|
|
@@ -23,6 +23,21 @@ import { Upload as TusUpload } from "tus-js-client";
|
|
|
23
23
|
|
|
24
24
|
const execFileAsync = promisify(execFile);
|
|
25
25
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
26
|
+
|
|
27
|
+
function imageBundleIdentity(env) {
|
|
28
|
+
const rawVersion = env?.SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_VERSION;
|
|
29
|
+
const bundleDigest = env?.SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_DIGEST;
|
|
30
|
+
if (rawVersion === undefined && bundleDigest === undefined) return null;
|
|
31
|
+
const bundleVersion = Number(rawVersion);
|
|
32
|
+
if (
|
|
33
|
+
!Number.isSafeInteger(bundleVersion) ||
|
|
34
|
+
bundleVersion < 1 ||
|
|
35
|
+
!SHA256.test(bundleDigest ?? "")
|
|
36
|
+
) {
|
|
37
|
+
throw new Error("runtime_bundle_identity_rejected");
|
|
38
|
+
}
|
|
39
|
+
return Object.freeze({ bundleVersion, bundleDigest });
|
|
40
|
+
}
|
|
26
41
|
const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
27
42
|
const FLY_OIDC_AUTHORIZATION_SCHEME = "FlyOIDC";
|
|
28
43
|
|
|
@@ -906,11 +921,13 @@ export function createFlyCustomerControlClient({
|
|
|
906
921
|
origin,
|
|
907
922
|
identity,
|
|
908
923
|
exchange,
|
|
924
|
+
env = process.env,
|
|
909
925
|
fetchImpl = fetch,
|
|
910
926
|
retryDelayMs = 150,
|
|
911
927
|
tusUploadImpl = uploadSupabaseSignedTus,
|
|
912
928
|
}) {
|
|
913
929
|
const endpoint = exactHttpsOrigin(origin);
|
|
930
|
+
const bundleIdentity = imageBundleIdentity(env);
|
|
914
931
|
if (
|
|
915
932
|
!Number.isSafeInteger(retryDelayMs) ||
|
|
916
933
|
retryDelayMs < 0 ||
|
|
@@ -919,6 +936,28 @@ export function createFlyCustomerControlClient({
|
|
|
919
936
|
throw new Error("worker_control_retry_delay_invalid");
|
|
920
937
|
}
|
|
921
938
|
const post = async (verb, path, body, { replaySafe = false } = {}) => {
|
|
939
|
+
const presentedBundle =
|
|
940
|
+
body && typeof body === "object" && !Array.isArray(body)
|
|
941
|
+
? imageBundleIdentity({
|
|
942
|
+
SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_VERSION:
|
|
943
|
+
body.bundleVersion === undefined
|
|
944
|
+
? undefined
|
|
945
|
+
: String(body.bundleVersion),
|
|
946
|
+
SELLABLE_AGENT_DEFAULT_PROFILE_BUNDLE_DIGEST: body.bundleDigest,
|
|
947
|
+
})
|
|
948
|
+
: null;
|
|
949
|
+
if (
|
|
950
|
+
presentedBundle &&
|
|
951
|
+
bundleIdentity &&
|
|
952
|
+
(presentedBundle.bundleVersion !== bundleIdentity.bundleVersion ||
|
|
953
|
+
presentedBundle.bundleDigest !== bundleIdentity.bundleDigest)
|
|
954
|
+
) {
|
|
955
|
+
throw new Error("runtime_bundle_identity_rejected");
|
|
956
|
+
}
|
|
957
|
+
const signedBody =
|
|
958
|
+
verb === "complete" && bundleIdentity && !presentedBundle
|
|
959
|
+
? { ...body, ...bundleIdentity }
|
|
960
|
+
: body;
|
|
922
961
|
const maxAttempts = replaySafe ? 3 : 1;
|
|
923
962
|
for (let attempt = 1; ; attempt += 1) {
|
|
924
963
|
try {
|
|
@@ -926,7 +965,7 @@ export function createFlyCustomerControlClient({
|
|
|
926
965
|
identity,
|
|
927
966
|
exchange,
|
|
928
967
|
verb,
|
|
929
|
-
body,
|
|
968
|
+
body: signedBody,
|
|
930
969
|
now: new Date(),
|
|
931
970
|
});
|
|
932
971
|
let response;
|
|
@@ -934,7 +973,7 @@ export function createFlyCustomerControlClient({
|
|
|
934
973
|
response = await fetchImpl(`${endpoint}${path}`, {
|
|
935
974
|
method: "POST",
|
|
936
975
|
headers: { authorization, "content-type": "application/json" },
|
|
937
|
-
body: JSON.stringify(
|
|
976
|
+
body: JSON.stringify(signedBody),
|
|
938
977
|
signal: AbortSignal.timeout(20_000),
|
|
939
978
|
});
|
|
940
979
|
} catch {
|
|
@@ -352,7 +352,7 @@ function publicInventory(inventory) {
|
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
const NATIVE_SCRIPT = String.raw`
|
|
355
|
-
import json, os, sys
|
|
355
|
+
import hashlib, json, os, sys
|
|
356
356
|
from pathlib import Path
|
|
357
357
|
request = json.loads(sys.stdin.read())
|
|
358
358
|
from tools.skills_tool import _find_all_skills, skill_view
|
|
@@ -360,6 +360,13 @@ from tools.skill_manager_tool import _find_skill, _create_skill, _edit_skill, _d
|
|
|
360
360
|
from hermes_cli.config import load_config
|
|
361
361
|
from hermes_cli.skills_config import get_disabled_skills, save_disabled_skills
|
|
362
362
|
|
|
363
|
+
def native_view(read_name, read_file=None):
|
|
364
|
+
viewed = skill_view(read_name, file_path=read_file, preprocess=False) if read_file else skill_view(read_name, preprocess=False)
|
|
365
|
+
content = viewed.get("content") if isinstance(viewed, dict) else viewed
|
|
366
|
+
success = viewed.get("success") is True if isinstance(viewed, dict) else isinstance(viewed, str)
|
|
367
|
+
name = str(viewed.get("name") or "") if isinstance(viewed, dict) else read_name
|
|
368
|
+
return {"success": success, "name": name, "content": content, "contentSha256": hashlib.sha256(content.encode("utf-8")).hexdigest() if isinstance(content, str) else None}
|
|
369
|
+
|
|
363
370
|
def inventory(read_name=None, read_file=None):
|
|
364
371
|
rows = _find_all_skills(skip_disabled=True)
|
|
365
372
|
config = load_config()
|
|
@@ -381,14 +388,14 @@ def inventory(read_name=None, read_file=None):
|
|
|
381
388
|
result.append(item)
|
|
382
389
|
viewed = None
|
|
383
390
|
if read_name:
|
|
384
|
-
viewed =
|
|
391
|
+
viewed = native_view(read_name, read_file)
|
|
385
392
|
return {"complete": True, "skills": result, "disabledNames": disabled, "disabledConfig": json.dumps(disabled, separators=(",", ":")), "nativeView": viewed}
|
|
386
393
|
|
|
387
394
|
op = request.get("operation")
|
|
388
395
|
if op == "enumerate":
|
|
389
396
|
output = inventory(request.get("readName"), request.get("readFile"))
|
|
390
397
|
elif op == "read":
|
|
391
|
-
output = {"nativeView":
|
|
398
|
+
output = {"nativeView": native_view(request["readName"], request.get("readFile"))}
|
|
392
399
|
elif op == "scan":
|
|
393
400
|
from tools.skills_guard import scan_skill, should_allow_install
|
|
394
401
|
# Phase 170 production receipt skills_apply_failed_rolled_back proved that
|
|
@@ -538,6 +545,18 @@ function findSkill(inventory, id, { editable = false } = {}) {
|
|
|
538
545
|
return skill;
|
|
539
546
|
}
|
|
540
547
|
|
|
548
|
+
function assertNativeView(proof, name, content, code) {
|
|
549
|
+
if (
|
|
550
|
+
proof?.success !== true ||
|
|
551
|
+
proof.name !== name ||
|
|
552
|
+
typeof proof.content !== "string" ||
|
|
553
|
+
proof.content !== content ||
|
|
554
|
+
proof.contentSha256 !== sha256(Buffer.from(content, "utf8"))
|
|
555
|
+
) {
|
|
556
|
+
fail(code);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
541
560
|
function backupTree(skillRoot) {
|
|
542
561
|
const snapshot = [];
|
|
543
562
|
const walk = (directory) => {
|
|
@@ -622,8 +641,12 @@ function executeLocked(request, paths, adapter, options) {
|
|
|
622
641
|
readName: skill.name,
|
|
623
642
|
readFile: relativePath === "SKILL.md" ? undefined : relativePath,
|
|
624
643
|
});
|
|
625
|
-
|
|
626
|
-
|
|
644
|
+
assertNativeView(
|
|
645
|
+
proof.nativeView,
|
|
646
|
+
skill.name,
|
|
647
|
+
content,
|
|
648
|
+
"skills_native_readback_failed"
|
|
649
|
+
);
|
|
627
650
|
atomicProof(paths, before);
|
|
628
651
|
return {
|
|
629
652
|
ok: true,
|
|
@@ -788,8 +811,7 @@ function executeLocked(request, paths, adapter, options) {
|
|
|
788
811
|
const after = normalizeEnumeration(afterNative, paths, options.limits);
|
|
789
812
|
if (
|
|
790
813
|
!after.complete ||
|
|
791
|
-
(request.operation !== "delete" &&
|
|
792
|
-
typeof afterNative.nativeView !== "string")
|
|
814
|
+
(request.operation !== "delete" && !afterNative.nativeView)
|
|
793
815
|
)
|
|
794
816
|
fail("skills_readback_failed");
|
|
795
817
|
const afterSkill =
|
|
@@ -798,6 +820,14 @@ function executeLocked(request, paths, adapter, options) {
|
|
|
798
820
|
request.operation === "delete" ? afterSkill !== null : afterSkill === null
|
|
799
821
|
)
|
|
800
822
|
fail("skills_readback_failed");
|
|
823
|
+
if (afterSkill) {
|
|
824
|
+
assertNativeView(
|
|
825
|
+
afterNative.nativeView,
|
|
826
|
+
afterSkill.name,
|
|
827
|
+
afterSkill.manifestContent,
|
|
828
|
+
"skills_readback_failed"
|
|
829
|
+
);
|
|
830
|
+
}
|
|
801
831
|
if (
|
|
802
832
|
(request.operation === "edit" &&
|
|
803
833
|
afterSkill.fileSha256 !==
|
|
@@ -933,6 +963,32 @@ export function executeExternalSkillsBridgeOperation(
|
|
|
933
963
|
});
|
|
934
964
|
}
|
|
935
965
|
|
|
966
|
+
export function proveDefaultProfileNativeSkills({
|
|
967
|
+
actions,
|
|
968
|
+
profileId,
|
|
969
|
+
dataRoot = SOUL_BRIDGE_DATA_ROOT,
|
|
970
|
+
} = {}) {
|
|
971
|
+
const managed = (actions ?? []).filter((action) => action.type === "skill");
|
|
972
|
+
if (managed.length === 0)
|
|
973
|
+
return { status: "EMPTY", digest: sha256("[]") };
|
|
974
|
+
const inventory = executeExternalSkillsBridgeOperation(
|
|
975
|
+
{ operation: "list" },
|
|
976
|
+
{ dataRoot, callerProfileId: profileId }
|
|
977
|
+
);
|
|
978
|
+
const proofs = managed.map((action) => {
|
|
979
|
+
const skill = inventory.skills.find((entry) => entry.name === action.id);
|
|
980
|
+
if (!skill) fail("default_profile_native_skill_missing");
|
|
981
|
+
const viewed = executeExternalSkillsBridgeOperation(
|
|
982
|
+
{ operation: "read", skillId: skill.id },
|
|
983
|
+
{ dataRoot, callerProfileId: profileId }
|
|
984
|
+
);
|
|
985
|
+
if (!viewed.ok || viewed.skill?.name !== action.id)
|
|
986
|
+
fail("default_profile_native_skill_readback_rejected");
|
|
987
|
+
return { id: action.id, fileSha256: viewed.fileSha256 };
|
|
988
|
+
});
|
|
989
|
+
return { status: "PROVEN", digest: sha256(stableJson(proofs)) };
|
|
990
|
+
}
|
|
991
|
+
|
|
936
992
|
export function computeSkillsConfigIdentity(profileRoot) {
|
|
937
993
|
if (!isAbsolute(profileRoot) || !existsSync(profileRoot))
|
|
938
994
|
fail("skills_profile_root_rejected");
|
|
@@ -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.
|
|
41
|
+
const INSTALLER_PACKAGE = "@sellable/install@0.1.643";
|
|
42
42
|
const MCP_PACKAGE = "@sellable/mcp@0.1.879";
|
|
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.
|
|
1550
|
+
"@sellable/install@0.1.643" ||
|
|
1551
1551
|
pinned.mcpPackage !== "@sellable/mcp@0.1.879" ||
|
|
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.
|
|
286
|
+
"@sellable/install@0.1.643" ||
|
|
287
287
|
desired.mcpPackage !== "@sellable/mcp@0.1.879" ||
|
|
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.
|
|
29
|
+
"@sellable/install@0.1.643";
|
|
30
30
|
export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.879";
|
|
31
31
|
const PINNED_INSTALL_VERSION = PINNED_INSTALL_PACKAGE.slice(
|
|
32
32
|
PINNED_INSTALL_PACKAGE.lastIndexOf("@") + 1
|