@themoltnet/agent-daemon 0.44.1 → 0.46.0
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/README.md +1 -1
- package/dist/cli.js +347 -201
- package/dist/pi.d.ts.map +1 -1
- package/dist/pi.js +557 -2
- package/package.json +10 -8
package/dist/pi.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../src/pi.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../src/pi.ts"],"names":[],"mappings":"AACA,OAAO,EASL,KAAK,mBAAmB,EACzB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,KAAK,EAAE,oBAAoB,EAAyB,MAAM,cAAc,CAAC;AAEhF,eAAO,MAAM,oBAAoB,kkBAIvB,CAAC;AAEX,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,mBAAmB,GAC3B,oBAAoB,CAkEtB;AAED,eAAO,MAAM,0BAA0B,qBASrC,CAAC;AAEH,eAAO,MAAM,sBAAsB,sBAElC,CAAC"}
|
package/dist/pi.js
CHANGED
|
@@ -1,5 +1,558 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { CID } from "multiformats/cid";
|
|
4
|
+
import * as json from "multiformats/codecs/json";
|
|
5
|
+
import { sha256 } from "multiformats/hashes/sha2";
|
|
6
|
+
import { Value } from "typebox/value";
|
|
2
7
|
import { GONDOLIN_BASE_EXECUTABLES, GONDOLIN_TOOL_NAMES, MOLTNET_TOOL_NAMES, agentSigningCapability, buildPiExecutorManifest, createPiTaskExecutor, defineGondolinTemplate, definePiRuntime } from "@themoltnet/pi-runtime";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import * as ed from "@noble/ed25519";
|
|
10
|
+
import { createHash as createHash$1, randomBytes } from "crypto";
|
|
11
|
+
import "multiformats/codecs/raw";
|
|
12
|
+
import "multiformats/hashes/digest";
|
|
13
|
+
import "@noble/hashes/sha2";
|
|
14
|
+
import "multiformats/bases/base32";
|
|
15
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
16
|
+
import "@ipld/dag-cbor";
|
|
17
|
+
import "@noble/ciphers/chacha";
|
|
18
|
+
import "@noble/hashes/hkdf";
|
|
19
|
+
//#region ../../libs/crypto-service/src/ssh.ts
|
|
20
|
+
/**
|
|
21
|
+
* SSH key format conversion for MoltNet Ed25519 keys
|
|
22
|
+
*
|
|
23
|
+
* Converts MoltNet agent keys (ed25519:<base64>) to OpenSSH format
|
|
24
|
+
* for use with git commit signing and SSH authentication.
|
|
25
|
+
*/
|
|
26
|
+
if (!ed.etc.sha512Sync) ed.etc.sha512Sync = (...m) => {
|
|
27
|
+
const hash = createHash$1("sha512");
|
|
28
|
+
m.forEach((msg) => hash.update(msg));
|
|
29
|
+
return hash.digest();
|
|
30
|
+
};
|
|
31
|
+
new TextEncoder();
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region ../../libs/crypto-service/src/crypto.service.ts
|
|
34
|
+
/**
|
|
35
|
+
* MoltNet Crypto Service
|
|
36
|
+
*
|
|
37
|
+
* Ed25519 cryptographic operations for agent identity
|
|
38
|
+
* Uses @noble/ed25519 for pure TypeScript implementation
|
|
39
|
+
*/
|
|
40
|
+
ed.etc.sha512Sync = (...m) => {
|
|
41
|
+
const hash = createHash$1("sha512");
|
|
42
|
+
m.forEach((msg) => hash.update(msg));
|
|
43
|
+
return hash.digest();
|
|
44
|
+
};
|
|
45
|
+
/** Domain-separation prefix for the signing payload envelope. */
|
|
46
|
+
var DOMAIN_PREFIX = "moltnet:v1";
|
|
47
|
+
/**
|
|
48
|
+
* Build deterministic signing bytes with domain separation and
|
|
49
|
+
* length-prefixed binary framing.
|
|
50
|
+
*
|
|
51
|
+
* Layout:
|
|
52
|
+
* UTF-8("moltnet:v1") || u32be(len(msg_hash)) || msg_hash || u32be(len(nonce_bytes)) || nonce_bytes
|
|
53
|
+
*
|
|
54
|
+
* Where msg_hash = SHA-256(UTF-8(message)).
|
|
55
|
+
*
|
|
56
|
+
* This produces a fixed-structure byte sequence immune to whitespace,
|
|
57
|
+
* newline, and encoding differences between runtimes.
|
|
58
|
+
*/
|
|
59
|
+
function buildSigningBytes(message, nonce) {
|
|
60
|
+
const msgHash = createHash$1("sha256").update(Buffer.from(message, "utf-8")).digest();
|
|
61
|
+
const nonceBytes = Buffer.from(nonce, "utf-8");
|
|
62
|
+
const prefix = Buffer.from(DOMAIN_PREFIX, "utf-8");
|
|
63
|
+
const buf = Buffer.alloc(prefix.length + 4 + msgHash.length + 4 + nonceBytes.length);
|
|
64
|
+
let offset = 0;
|
|
65
|
+
prefix.copy(buf, offset);
|
|
66
|
+
offset += prefix.length;
|
|
67
|
+
buf.writeUInt32BE(msgHash.length, offset);
|
|
68
|
+
offset += 4;
|
|
69
|
+
msgHash.copy(buf, offset);
|
|
70
|
+
offset += msgHash.length;
|
|
71
|
+
buf.writeUInt32BE(nonceBytes.length, offset);
|
|
72
|
+
offset += 4;
|
|
73
|
+
nonceBytes.copy(buf, offset);
|
|
74
|
+
return new Uint8Array(buf);
|
|
75
|
+
}
|
|
76
|
+
var cryptoService = {
|
|
77
|
+
async generateKeyPair() {
|
|
78
|
+
const privateKeyBytes = ed.utils.randomPrivateKey();
|
|
79
|
+
const publicKeyBytes = await ed.getPublicKeyAsync(privateKeyBytes);
|
|
80
|
+
const privateKey = Buffer.from(privateKeyBytes).toString("base64");
|
|
81
|
+
return {
|
|
82
|
+
publicKey: `ed25519:${Buffer.from(publicKeyBytes).toString("base64")}`,
|
|
83
|
+
privateKey,
|
|
84
|
+
fingerprint: this.generateFingerprint(publicKeyBytes)
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
generateFingerprint(publicKeyBytes) {
|
|
88
|
+
return (createHash$1("sha256").update(publicKeyBytes).digest("hex").slice(0, 16).toUpperCase().match(/.{4}/g) ?? []).join("-");
|
|
89
|
+
},
|
|
90
|
+
parsePublicKey(publicKey) {
|
|
91
|
+
const base64 = publicKey.replace(/^ed25519:/, "");
|
|
92
|
+
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
93
|
+
},
|
|
94
|
+
async sign(message, privateKeyBase64) {
|
|
95
|
+
const privateKeyBytes = new Uint8Array(Buffer.from(privateKeyBase64, "base64"));
|
|
96
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
97
|
+
const signature = await ed.signAsync(messageBytes, privateKeyBytes);
|
|
98
|
+
return Buffer.from(signature).toString("base64");
|
|
99
|
+
},
|
|
100
|
+
async verify(message, signature, publicKey) {
|
|
101
|
+
try {
|
|
102
|
+
const publicKeyBytes = this.parsePublicKey(publicKey);
|
|
103
|
+
const signatureBytes = new Uint8Array(Buffer.from(signature, "base64"));
|
|
104
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
105
|
+
return await ed.verifyAsync(signatureBytes, messageBytes, publicKeyBytes);
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
async signWithNonce(message, nonce, privateKeyBase64) {
|
|
111
|
+
const privateKeyBytes = new Uint8Array(Buffer.from(privateKeyBase64, "base64"));
|
|
112
|
+
const signingBytes = buildSigningBytes(message, nonce);
|
|
113
|
+
const signature = await ed.signAsync(signingBytes, privateKeyBytes);
|
|
114
|
+
return Buffer.from(signature).toString("base64");
|
|
115
|
+
},
|
|
116
|
+
async verifyWithNonce(message, nonce, signature, publicKey) {
|
|
117
|
+
try {
|
|
118
|
+
const publicKeyBytes = this.parsePublicKey(publicKey);
|
|
119
|
+
const signatureBytes = new Uint8Array(Buffer.from(signature, "base64"));
|
|
120
|
+
const signingBytes = buildSigningBytes(message, nonce);
|
|
121
|
+
return await ed.verifyAsync(signatureBytes, signingBytes, publicKeyBytes);
|
|
122
|
+
} catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
async createSignedMessage(message, privateKeyBase64, publicKey) {
|
|
127
|
+
return {
|
|
128
|
+
message,
|
|
129
|
+
signature: await this.sign(message, privateKeyBase64),
|
|
130
|
+
publicKey
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
async verifySignedMessage(signedMessage) {
|
|
134
|
+
return this.verify(signedMessage.message, signedMessage.signature, signedMessage.publicKey);
|
|
135
|
+
},
|
|
136
|
+
generateChallenge() {
|
|
137
|
+
return `moltnet:challenge:${randomBytes(32).toString("hex")}:${Date.now()}`;
|
|
138
|
+
},
|
|
139
|
+
async derivePublicKey(privateKeyBase64) {
|
|
140
|
+
const privateKeyBytes = new Uint8Array(Buffer.from(privateKeyBase64, "base64"));
|
|
141
|
+
const publicKeyBytes = await ed.getPublicKeyAsync(privateKeyBytes);
|
|
142
|
+
return `ed25519:${Buffer.from(publicKeyBytes).toString("base64")}`;
|
|
143
|
+
},
|
|
144
|
+
getFingerprintFromPublicKey(publicKey) {
|
|
145
|
+
const publicKeyBytes = this.parsePublicKey(publicKey);
|
|
146
|
+
return this.generateFingerprint(publicKeyBytes);
|
|
147
|
+
},
|
|
148
|
+
deriveX25519PrivateKey(ed25519PrivateKeyBase64) {
|
|
149
|
+
const seed = new Uint8Array(Buffer.from(ed25519PrivateKeyBase64, "base64"));
|
|
150
|
+
const x25519Priv = ed25519.utils.toMontgomerySecret(seed);
|
|
151
|
+
return Buffer.from(x25519Priv).toString("base64");
|
|
152
|
+
},
|
|
153
|
+
deriveX25519PublicKey(ed25519PublicKey) {
|
|
154
|
+
const edPubBytes = this.parsePublicKey(ed25519PublicKey);
|
|
155
|
+
const x25519Pub = ed25519.utils.toMontgomery(edPubBytes);
|
|
156
|
+
return `x25519:${Buffer.from(x25519Pub).toString("base64")}`;
|
|
157
|
+
},
|
|
158
|
+
async createIdentityProof(identityId, privateKeyBase64) {
|
|
159
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
160
|
+
const message = `moltnet:register:${identityId}:${timestamp}`;
|
|
161
|
+
return {
|
|
162
|
+
message,
|
|
163
|
+
signature: await this.sign(message, privateKeyBase64),
|
|
164
|
+
timestamp
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
async verifyIdentityProof(proof, publicKey, expectedIdentityId) {
|
|
168
|
+
if (!await this.verify(proof.message, proof.signature, publicKey)) return false;
|
|
169
|
+
const expectedPrefix = `moltnet:register:${expectedIdentityId}:`;
|
|
170
|
+
if (!proof.message.startsWith(expectedPrefix)) return false;
|
|
171
|
+
const proofTime = new Date(proof.timestamp).getTime();
|
|
172
|
+
if (Date.now() - proofTime > 300 * 1e3) return false;
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region ../../libs/crypto-service/src/executor-attestation.ts
|
|
178
|
+
ed.etc.sha512Sync = (...m) => {
|
|
179
|
+
const hash = createHash("sha512");
|
|
180
|
+
m.forEach((msg) => hash.update(msg));
|
|
181
|
+
return hash.digest();
|
|
182
|
+
};
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region ../../libs/crypto-service/src/json-cid.ts
|
|
185
|
+
/**
|
|
186
|
+
* Generic JSON CID — CIDv1 for arbitrary JSON-serialisable values.
|
|
187
|
+
*
|
|
188
|
+
* Uses the dag-json codec and sha2-256, producing a base32lower CIDv1.
|
|
189
|
+
* Suitable for content-addressing task inputs, schema objects, and other
|
|
190
|
+
* JSON payloads that don't need diary-entry canonical normalisation.
|
|
191
|
+
*/
|
|
192
|
+
async function computeJsonCid(value) {
|
|
193
|
+
const bytes = json.encode(value);
|
|
194
|
+
const hash = await sha256.digest(bytes);
|
|
195
|
+
return CID.create(1, json.code, hash).toString();
|
|
196
|
+
}
|
|
197
|
+
new TextEncoder().encode("SSHSIG");
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region ../../libs/execution-plan/src/control-ids.ts
|
|
200
|
+
var CREDENTIAL_AUTHORITY_PREFIX = "credential:";
|
|
201
|
+
var CREDENTIAL_PROJECTION_PREFIX = "credential-projection:";
|
|
202
|
+
var HOST_CAPABILITY_PREFIX = "host-capability:";
|
|
203
|
+
function credentialAuthorityControl(name) {
|
|
204
|
+
return `${CREDENTIAL_AUTHORITY_PREFIX}${name}`;
|
|
205
|
+
}
|
|
206
|
+
function credentialProjectionControl(projection) {
|
|
207
|
+
return `${CREDENTIAL_PROJECTION_PREFIX}${projection}`;
|
|
208
|
+
}
|
|
209
|
+
function hostCapabilityControl(name) {
|
|
210
|
+
return `${HOST_CAPABILITY_PREFIX}${name}`;
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region ../../libs/execution-plan/src/compile-execution-plan.ts
|
|
214
|
+
/**
|
|
215
|
+
* Compile resolved authority and portable intent against one executor offer.
|
|
216
|
+
* The compiler never reads policy identifiers, host bindings, provider
|
|
217
|
+
* coordinates, runtime manifests, or implementation names.
|
|
218
|
+
*/
|
|
219
|
+
function compileExecutionPlan(input) {
|
|
220
|
+
const { intent, offer } = input;
|
|
221
|
+
const stamp = {
|
|
222
|
+
mode: intent.mode,
|
|
223
|
+
phase: "preflight",
|
|
224
|
+
basis: "declared"
|
|
225
|
+
};
|
|
226
|
+
const decisions = [];
|
|
227
|
+
const deliverables = [];
|
|
228
|
+
const readinessByName = uniqueReadiness(input.credentialReadiness);
|
|
229
|
+
const offersByControl = groupOffers(offer.controls);
|
|
230
|
+
const authorized = intent.authority.authorizedControls;
|
|
231
|
+
const networkPatterns = [...intent.network.allowedHosts, ...intent.network.allowedInternalHosts];
|
|
232
|
+
let blocked = false;
|
|
233
|
+
for (const requirement of intent.credentialRequirements) {
|
|
234
|
+
const control = credentialAuthorityControl(requirement.name);
|
|
235
|
+
const fail = (reason, state) => {
|
|
236
|
+
const decisionState = state ?? (requirement.required ? "failed" : "degraded");
|
|
237
|
+
decisions.push({
|
|
238
|
+
control,
|
|
239
|
+
state: decisionState,
|
|
240
|
+
...stamp,
|
|
241
|
+
reason
|
|
242
|
+
});
|
|
243
|
+
blocked ||= requirement.required;
|
|
244
|
+
};
|
|
245
|
+
if (authorized === void 0) {
|
|
246
|
+
fail("credential_authority_unresolved");
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (!authorized.includes(control)) {
|
|
250
|
+
fail("credential_authority_denied");
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const readiness = readinessByName.get(requirement.name);
|
|
254
|
+
if (readiness === void 0) {
|
|
255
|
+
fail("credential_readiness_missing");
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (readiness.status !== "ready") {
|
|
259
|
+
fail(readiness.status);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (requirement.destinations.some((destination) => !hostCovered(destination.host, networkPatterns))) {
|
|
263
|
+
fail("destination_not_in_network_intent");
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (requirement.lifecycle !== void 0 && requirement.lifecycle.maxTtlSec > intent.lease.ttlSec) {
|
|
267
|
+
fail("lifecycle_exceeds_lease");
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const offerControl = credentialProjectionControl(requirement.projection);
|
|
271
|
+
const candidates = offersByControl.get(offerControl) ?? [];
|
|
272
|
+
const matchingCandidates = candidates.filter((candidate) => offerContainsRequirement(candidate, requirement));
|
|
273
|
+
if (matchingCandidates.length !== 1) {
|
|
274
|
+
fail(candidates.length === 0 ? "control_not_offered" : matchingCandidates.length === 0 ? "offer_constraints_mismatch" : "offer_ambiguous", "unsupported");
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const candidate = matchingCandidates[0];
|
|
278
|
+
decisions.push({
|
|
279
|
+
control,
|
|
280
|
+
state: "enforced",
|
|
281
|
+
...stamp,
|
|
282
|
+
offerControl,
|
|
283
|
+
enforcement: candidate.enforcement,
|
|
284
|
+
locus: candidate.locus
|
|
285
|
+
});
|
|
286
|
+
deliverables.push({
|
|
287
|
+
name: requirement.name,
|
|
288
|
+
projection: requirement.projection,
|
|
289
|
+
...requirement.projection === "brokered-http" && { guestEnv: requirement.guestEnv },
|
|
290
|
+
required: requirement.required,
|
|
291
|
+
destinations: cloneDestinations(requirement.destinations),
|
|
292
|
+
offerControl,
|
|
293
|
+
enforcement: candidate.enforcement,
|
|
294
|
+
locus: candidate.locus
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
for (const name of intent.requiredCapabilities) {
|
|
298
|
+
const control = hostCapabilityControl(name);
|
|
299
|
+
if (authorized === void 0) {
|
|
300
|
+
decisions.push({
|
|
301
|
+
control,
|
|
302
|
+
state: "failed",
|
|
303
|
+
...stamp,
|
|
304
|
+
reason: "capability_authority_unresolved"
|
|
305
|
+
});
|
|
306
|
+
blocked = true;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (!authorized.includes(control)) {
|
|
310
|
+
decisions.push({
|
|
311
|
+
control,
|
|
312
|
+
state: "failed",
|
|
313
|
+
...stamp,
|
|
314
|
+
reason: "capability_authority_denied"
|
|
315
|
+
});
|
|
316
|
+
blocked = true;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const candidates = offersByControl.get(control) ?? [];
|
|
320
|
+
if (candidates.length !== 1) {
|
|
321
|
+
decisions.push({
|
|
322
|
+
control,
|
|
323
|
+
state: "unsupported",
|
|
324
|
+
...stamp,
|
|
325
|
+
reason: candidates.length === 0 ? "control_not_offered" : "offer_ambiguous"
|
|
326
|
+
});
|
|
327
|
+
blocked = true;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const candidate = candidates[0];
|
|
331
|
+
decisions.push({
|
|
332
|
+
control,
|
|
333
|
+
state: "enforced",
|
|
334
|
+
...stamp,
|
|
335
|
+
offerControl: candidate.id,
|
|
336
|
+
enforcement: candidate.enforcement,
|
|
337
|
+
locus: candidate.locus
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
for (const control of intent.lease.requiredControls) {
|
|
341
|
+
const candidates = offersByControl.get(control) ?? [];
|
|
342
|
+
if (candidates.length !== 1) {
|
|
343
|
+
decisions.push({
|
|
344
|
+
control,
|
|
345
|
+
state: "unsupported",
|
|
346
|
+
...stamp,
|
|
347
|
+
reason: candidates.length === 0 ? "control_not_offered" : "offer_ambiguous"
|
|
348
|
+
});
|
|
349
|
+
blocked = true;
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const candidate = candidates[0];
|
|
353
|
+
decisions.push({
|
|
354
|
+
control,
|
|
355
|
+
state: "enforced",
|
|
356
|
+
...stamp,
|
|
357
|
+
offerControl: candidate.id,
|
|
358
|
+
enforcement: candidate.enforcement,
|
|
359
|
+
locus: candidate.locus
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
mode: intent.mode,
|
|
364
|
+
launchable: !blocked,
|
|
365
|
+
executor: { ...offer.executor },
|
|
366
|
+
decisions,
|
|
367
|
+
deliverables,
|
|
368
|
+
effectiveNetwork: {
|
|
369
|
+
allowedHosts: [...new Set(intent.network.allowedHosts)].sort(),
|
|
370
|
+
allowedInternalHosts: [...new Set(intent.network.allowedInternalHosts)].sort()
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function groupOffers(controls) {
|
|
375
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
376
|
+
for (const control of controls) {
|
|
377
|
+
const existing = grouped.get(control.id) ?? [];
|
|
378
|
+
existing.push(control);
|
|
379
|
+
grouped.set(control.id, existing);
|
|
380
|
+
}
|
|
381
|
+
return grouped;
|
|
382
|
+
}
|
|
383
|
+
function uniqueReadiness(readiness) {
|
|
384
|
+
const result = /* @__PURE__ */ new Map();
|
|
385
|
+
for (const record of readiness) {
|
|
386
|
+
if (result.has(record.name)) throw new Error(`duplicate credential readiness for "${record.name}"`);
|
|
387
|
+
result.set(record.name, record);
|
|
388
|
+
}
|
|
389
|
+
return result;
|
|
390
|
+
}
|
|
391
|
+
function offerContainsRequirement(offer, requirement) {
|
|
392
|
+
const offeredDestinations = offer.constraints?.destinations;
|
|
393
|
+
if (offeredDestinations === void 0 || !sameDestinationSet(offeredDestinations, requirement.destinations)) return false;
|
|
394
|
+
if (requirement.projection === "brokered-http") return offer.constraints?.guestEnvs?.includes(requirement.guestEnv) ?? false;
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
function sameDestinationSet(offered, requested) {
|
|
398
|
+
const offeredKeys = offered.map(destinationKey).sort();
|
|
399
|
+
const requestedKeys = requested.map(destinationKey).sort();
|
|
400
|
+
return offeredKeys.length === requestedKeys.length && offeredKeys.every((key, index) => key === requestedKeys[index]);
|
|
401
|
+
}
|
|
402
|
+
function destinationKey(destination) {
|
|
403
|
+
return `${destination.protocol}\0${destination.host}\0${destination.port}`;
|
|
404
|
+
}
|
|
405
|
+
function cloneDestinations(destinations) {
|
|
406
|
+
return destinations.map((destination) => ({ ...destination }));
|
|
407
|
+
}
|
|
408
|
+
/** Exact host, or a profile wildcard covering one or more subdomains. */
|
|
409
|
+
function hostCovered(host, patterns) {
|
|
410
|
+
return patterns.some((pattern) => {
|
|
411
|
+
if (pattern === host) return true;
|
|
412
|
+
if (!pattern.startsWith("*.")) return false;
|
|
413
|
+
const suffix = pattern.slice(1);
|
|
414
|
+
return host.endsWith(suffix) && host.length > suffix.length;
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
//#endregion
|
|
418
|
+
//#region ../../libs/execution-plan/src/credential-requirements.ts
|
|
419
|
+
var CredentialDestination = Type.Object({
|
|
420
|
+
protocol: Type.Union([Type.Literal("https"), Type.Literal("http")]),
|
|
421
|
+
host: Type.String({
|
|
422
|
+
minLength: 1,
|
|
423
|
+
maxLength: 255,
|
|
424
|
+
pattern: "^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))*$"
|
|
425
|
+
}),
|
|
426
|
+
port: Type.Integer({
|
|
427
|
+
minimum: 1,
|
|
428
|
+
maximum: 65535
|
|
429
|
+
})
|
|
430
|
+
}, { additionalProperties: false });
|
|
431
|
+
var GuestEnvName = Type.String({
|
|
432
|
+
minLength: 1,
|
|
433
|
+
maxLength: 128,
|
|
434
|
+
pattern: "^(?!MOLTNET_)[A-Z][A-Z0-9_]*$"
|
|
435
|
+
});
|
|
436
|
+
var CredentialLifecycleIntent = Type.Object({
|
|
437
|
+
maxTtlSec: Type.Integer({
|
|
438
|
+
minimum: 1,
|
|
439
|
+
maximum: 86400
|
|
440
|
+
}),
|
|
441
|
+
refreshBeforeSec: Type.Integer({
|
|
442
|
+
minimum: 1,
|
|
443
|
+
maximum: 86400
|
|
444
|
+
})
|
|
445
|
+
}, { additionalProperties: false });
|
|
446
|
+
var credentialRequirementBase = {
|
|
447
|
+
name: Type.String({ minLength: 1 }),
|
|
448
|
+
kind: Type.Union([
|
|
449
|
+
Type.Literal("http-bearer"),
|
|
450
|
+
Type.Literal("http-basic"),
|
|
451
|
+
Type.Literal("api-key-header")
|
|
452
|
+
]),
|
|
453
|
+
destinations: Type.Array(CredentialDestination, { minItems: 1 }),
|
|
454
|
+
required: Type.Boolean({ default: true }),
|
|
455
|
+
lifecycle: Type.Optional(CredentialLifecycleIntent)
|
|
456
|
+
};
|
|
457
|
+
var HostToolCredentialRequirement = Type.Object({
|
|
458
|
+
...credentialRequirementBase,
|
|
459
|
+
projection: Type.Literal("host-tool")
|
|
460
|
+
}, { additionalProperties: false });
|
|
461
|
+
var BrokeredHttpCredentialRequirement = Type.Object({
|
|
462
|
+
...credentialRequirementBase,
|
|
463
|
+
projection: Type.Literal("brokered-http"),
|
|
464
|
+
guestEnv: GuestEnvName
|
|
465
|
+
}, { additionalProperties: false });
|
|
466
|
+
var CredentialRequirement = Type.Union([HostToolCredentialRequirement, BrokeredHttpCredentialRequirement]);
|
|
467
|
+
function parseCredentialRequirements(input) {
|
|
468
|
+
const withDefaults = Value.Default(Type.Array(CredentialRequirement), Value.Clone(input));
|
|
469
|
+
const requirements = Value.Parse(Type.Array(CredentialRequirement), withDefaults);
|
|
470
|
+
const names = /* @__PURE__ */ new Set();
|
|
471
|
+
const guestEnvs = /* @__PURE__ */ new Set();
|
|
472
|
+
for (const requirement of requirements) {
|
|
473
|
+
if (names.has(requirement.name)) throw new Error(`duplicate credential requirement name "${requirement.name}"`);
|
|
474
|
+
names.add(requirement.name);
|
|
475
|
+
if (requirement.projection === "brokered-http") {
|
|
476
|
+
if (guestEnvs.has(requirement.guestEnv)) throw new Error(`duplicate brokered guestEnv "${requirement.guestEnv}"`);
|
|
477
|
+
guestEnvs.add(requirement.guestEnv);
|
|
478
|
+
}
|
|
479
|
+
const lifecycle = requirement.lifecycle;
|
|
480
|
+
if (lifecycle && lifecycle.refreshBeforeSec >= lifecycle.maxTtlSec) throw new Error(`credential requirement "${requirement.name}": refreshBeforeSec must be shorter than maxTtlSec`);
|
|
481
|
+
}
|
|
482
|
+
return requirements;
|
|
483
|
+
}
|
|
484
|
+
//#endregion
|
|
485
|
+
//#region ../../libs/execution-plan/src/execution-snapshot.ts
|
|
486
|
+
/**
|
|
487
|
+
* Create an immutable, content-addressed, value-free execution snapshot. The
|
|
488
|
+
* existing policy snapshot hash is pinned inside intent; policy composition
|
|
489
|
+
* has already happened before this boundary.
|
|
490
|
+
*/
|
|
491
|
+
async function createExecutionPlanSnapshot(input) {
|
|
492
|
+
const body = structuredClone(input);
|
|
493
|
+
return deepFreeze({
|
|
494
|
+
cid: await computeJsonCid(snapshotBody(body)),
|
|
495
|
+
...body
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
function snapshotBody(input) {
|
|
499
|
+
return {
|
|
500
|
+
v: "moltnet:execution-plan-snapshot:v1",
|
|
501
|
+
...input
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function deepFreeze(value) {
|
|
505
|
+
if (value !== null && typeof value === "object") {
|
|
506
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
507
|
+
Object.freeze(value);
|
|
508
|
+
}
|
|
509
|
+
return value;
|
|
510
|
+
}
|
|
511
|
+
//#endregion
|
|
512
|
+
//#region src/lib/runtime-governance.ts
|
|
513
|
+
var executionOffers = /* @__PURE__ */ new WeakMap();
|
|
514
|
+
/** Attach private governance metadata without widening the public adapter API. */
|
|
515
|
+
function registerRuntimeExecutionOffer(runtime, factory) {
|
|
516
|
+
executionOffers.set(runtime, factory);
|
|
517
|
+
}
|
|
518
|
+
/** Resolve the selected runtime's offer for the observe-only orchestrator. */
|
|
519
|
+
function runtimeExecutionOffer(runtime, executorFingerprint) {
|
|
520
|
+
return executionOffers.get(runtime)?.(executorFingerprint);
|
|
521
|
+
}
|
|
522
|
+
//#endregion
|
|
523
|
+
//#region ../../libs/execution-integrations/src/pi.ts
|
|
524
|
+
/** Convert the canonical Pi manifest to a portable, open-ended offer. */
|
|
525
|
+
function executionCapabilityOfferFromPiManifest(manifest, options) {
|
|
526
|
+
const enforcement = options.enforcement ?? "native";
|
|
527
|
+
const locus = options.locus ?? "pi:isolated-runtime";
|
|
528
|
+
return {
|
|
529
|
+
executor: {
|
|
530
|
+
id: `${manifest.runtime.id}@${manifest.runtime.version}`,
|
|
531
|
+
fingerprint: options.executorFingerprint
|
|
532
|
+
},
|
|
533
|
+
controls: [...(manifest.brokeredHttpSecrets ?? []).map((secret) => ({
|
|
534
|
+
id: credentialProjectionControl("brokered-http"),
|
|
535
|
+
enforcement,
|
|
536
|
+
locus,
|
|
537
|
+
constraints: {
|
|
538
|
+
destinations: manifestDestinations(secret),
|
|
539
|
+
guestEnvs: [secret.guestEnv]
|
|
540
|
+
}
|
|
541
|
+
})), ...(manifest.hostCapabilities ?? []).map((capability) => ({
|
|
542
|
+
id: hostCapabilityControl(capability.name),
|
|
543
|
+
enforcement,
|
|
544
|
+
locus
|
|
545
|
+
}))]
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function manifestDestinations(secret) {
|
|
549
|
+
return secret.hosts.flatMap((host) => secret.ports.map((port) => ({
|
|
550
|
+
protocol: secret.protocol,
|
|
551
|
+
host,
|
|
552
|
+
port
|
|
553
|
+
})));
|
|
554
|
+
}
|
|
555
|
+
//#endregion
|
|
3
556
|
//#region src/pi.ts
|
|
4
557
|
var PI_KERNEL_TOOL_NAMES = [
|
|
5
558
|
...GONDOLIN_TOOL_NAMES,
|
|
@@ -29,7 +582,7 @@ function createPiDaemonAdapter(runtime) {
|
|
|
29
582
|
builtInToolNames: PI_KERNEL_TOOL_NAMES
|
|
30
583
|
});
|
|
31
584
|
const extensionTools = runtime.extensions.flatMap((extension) => extension.declaredTools);
|
|
32
|
-
|
|
585
|
+
const prepared = {
|
|
33
586
|
runtimeKind: runtime.runtimeKind,
|
|
34
587
|
manifest,
|
|
35
588
|
tools: [
|
|
@@ -49,6 +602,8 @@ function createPiDaemonAdapter(runtime) {
|
|
|
49
602
|
resolvedVmTemplate: resolvedTemplate
|
|
50
603
|
})
|
|
51
604
|
};
|
|
605
|
+
registerRuntimeExecutionOffer(prepared, (executorFingerprint) => executionCapabilityOfferFromPiManifest(manifest, { executorFingerprint }));
|
|
606
|
+
return prepared;
|
|
52
607
|
}
|
|
53
608
|
};
|
|
54
609
|
}
|
|
@@ -64,4 +619,4 @@ var defaultPiRuntimeDefinition = definePiRuntime({
|
|
|
64
619
|
});
|
|
65
620
|
var defaultPiDaemonAdapter = createPiDaemonAdapter(defaultPiRuntimeDefinition);
|
|
66
621
|
//#endregion
|
|
67
|
-
export { PI_KERNEL_TOOL_NAMES, createPiDaemonAdapter, defaultPiDaemonAdapter, defaultPiRuntimeDefinition };
|
|
622
|
+
export { PI_KERNEL_TOOL_NAMES, cryptoService as a, createPiDaemonAdapter, defaultPiDaemonAdapter, defaultPiRuntimeDefinition, compileExecutionPlan as i, createExecutionPlanSnapshot as n, parseCredentialRequirements as r, runtimeExecutionOffer as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/agent-daemon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Universal MoltNet agent daemon host with a built-in Pi/Gondolin runtime and support for trusted operator-owned runtime modules. CLI: moltnet-agent.",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@noble/curves": "^2.0.0",
|
|
57
57
|
"@noble/ed25519": "^2.0.0",
|
|
58
58
|
"@noble/hashes": "^1.7.0",
|
|
59
|
-
"@opentelemetry/api": "^1.9.
|
|
59
|
+
"@opentelemetry/api": "^1.9.1",
|
|
60
60
|
"@opentelemetry/exporter-metrics-otlp-proto": "^0.212.0",
|
|
61
61
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.212.0",
|
|
62
62
|
"@opentelemetry/instrumentation": "^0.212.0",
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
"pino-opentelemetry-transport": "^3.0.0",
|
|
79
79
|
"pino-pretty": "^13.1.3",
|
|
80
80
|
"typebox": "^1.2.8",
|
|
81
|
-
"@themoltnet/agent-runtime": "0.
|
|
82
|
-
"@themoltnet/os-keyring": "0.
|
|
83
|
-
"@themoltnet/pi-runtime": "0.
|
|
84
|
-
"@themoltnet/sdk": "0.
|
|
81
|
+
"@themoltnet/agent-runtime": "0.45.1",
|
|
82
|
+
"@themoltnet/os-keyring": "0.3.0",
|
|
83
|
+
"@themoltnet/pi-runtime": "0.13.0",
|
|
84
|
+
"@themoltnet/sdk": "0.139.0"
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"tsx": "^4.7.0",
|
|
@@ -91,10 +91,12 @@
|
|
|
91
91
|
"vitest": "^3.0.0",
|
|
92
92
|
"@moltnet/bootstrap": "0.1.0",
|
|
93
93
|
"@moltnet/crypto-service": "0.1.0",
|
|
94
|
+
"@moltnet/execution-integrations": "0.1.0",
|
|
94
95
|
"@moltnet/models": "0.1.0",
|
|
96
|
+
"@moltnet/execution-plan": "0.1.0",
|
|
95
97
|
"@moltnet/observability": "0.1.0",
|
|
96
|
-
"@moltnet/
|
|
97
|
-
"@moltnet/
|
|
98
|
+
"@moltnet/tasks": "0.1.0",
|
|
99
|
+
"@moltnet/runtime-profiles": "0.1.0"
|
|
98
100
|
},
|
|
99
101
|
"nx": {
|
|
100
102
|
"projectType": "application",
|