@forgezero/agent 0.1.80 → 0.1.81
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 +2 -1
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.js +1 -1
- package/dist/cli/app-build.d.ts +10 -0
- package/dist/fz-agent.js +56 -17
- package/dist/fz.js +81 -15
- package/dist/metal-bootstrap.js +13 -1
- package/dist/metal-helper-socket.js +55 -16
- package/dist/metal-provision.d.ts +6 -3
- package/dist/metal-provision.js +55 -16
- package/dist/operator-bootstrap.js +13 -1
- package/dist/platform-fleet-verification.js +1 -1
- package/dist/provision.js +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1511,7 +1511,7 @@ export const selectedCapability = METAL_BOOTSTRAP_STATE_PATH;
|
|
|
1511
1511
|
<a id="forgezero-agent-metal-provision"></a>
|
|
1512
1512
|
## @forgezero/agent/metal-provision
|
|
1513
1513
|
|
|
1514
|
-
The single constrained compute materializer for platform genesis, platform scaling and tenant compute. It executes caller-supplied OS, CPU, memory, disk, bandwidth, network and confidentiality coordinates after capacity and image validation; it owns no environment or product-tier shape. This entry exposes 11 named value exports and
|
|
1514
|
+
The single constrained compute materializer for platform genesis, platform scaling and tenant compute. It executes caller-supplied OS, CPU, memory, disk, bandwidth, network and confidentiality coordinates after capacity and image validation; it owns no environment or product-tier shape. This entry exposes 11 named value exports and 7 named type exports. The generated import block lists one name per line for scanning and copying; keep only the names used by your file.
|
|
1515
1515
|
|
|
1516
1516
|
```text
|
|
1517
1517
|
import {
|
|
@@ -1531,6 +1531,7 @@ import {
|
|
|
1531
1531
|
import type {
|
|
1532
1532
|
GuestManifest,
|
|
1533
1533
|
MetalCommandResult,
|
|
1534
|
+
MetalCpuAllocation,
|
|
1534
1535
|
MetalCpuPool,
|
|
1535
1536
|
MetalExec,
|
|
1536
1537
|
MetalImage,
|
package/dist/agent-heartbeat.js
CHANGED
package/dist/bootstrap.js
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface AppBuildPlan {
|
|
2
|
+
cwd: string;
|
|
3
|
+
command: readonly string[];
|
|
4
|
+
}
|
|
5
|
+
/** Resolve a checkout without teaching the globally installed CLI any App internals. */
|
|
6
|
+
export declare function planAppBuild(input: {
|
|
7
|
+
root: string;
|
|
8
|
+
profile?: string;
|
|
9
|
+
api?: string;
|
|
10
|
+
}): AppBuildPlan;
|
package/dist/fz-agent.js
CHANGED
|
@@ -9331,7 +9331,7 @@ async function writeAndCloseProcessInput(input, value) {
|
|
|
9331
9331
|
}
|
|
9332
9332
|
|
|
9333
9333
|
// src/version.ts
|
|
9334
|
-
var VERSION2 = "0.1.
|
|
9334
|
+
var VERSION2 = "0.1.81";
|
|
9335
9335
|
|
|
9336
9336
|
// src/ssh-bootstrap.ts
|
|
9337
9337
|
class SshBootstrapError extends Error {
|
|
@@ -10208,6 +10208,32 @@ function membersOfLinuxList(value, label) {
|
|
|
10208
10208
|
throw new MetalProvisionError(`${label} list overlaps itself`);
|
|
10209
10209
|
return members;
|
|
10210
10210
|
}
|
|
10211
|
+
function linuxList(members) {
|
|
10212
|
+
const sorted = [...new Set(members)].sort((left, right) => left - right);
|
|
10213
|
+
const ranges = [];
|
|
10214
|
+
for (let index = 0;index < sorted.length; ) {
|
|
10215
|
+
const start = sorted[index];
|
|
10216
|
+
let end = start;
|
|
10217
|
+
while (index + 1 < sorted.length && sorted[index + 1] === end + 1) {
|
|
10218
|
+
index += 1;
|
|
10219
|
+
end = sorted[index];
|
|
10220
|
+
}
|
|
10221
|
+
ranges.push(start === end ? String(start) : `${start}-${end}`);
|
|
10222
|
+
index += 1;
|
|
10223
|
+
}
|
|
10224
|
+
return ranges.join(",");
|
|
10225
|
+
}
|
|
10226
|
+
function physicalCoreGroups(pool) {
|
|
10227
|
+
const cpus = membersOfLinuxList(pool.cpus, "CPU");
|
|
10228
|
+
if (cpus.length % pool.physicalCores !== 0) {
|
|
10229
|
+
throw new MetalProvisionError("CPU pool threads must divide evenly across physical cores");
|
|
10230
|
+
}
|
|
10231
|
+
const threadsPerCore = cpus.length / pool.physicalCores;
|
|
10232
|
+
if (!Number.isInteger(threadsPerCore) || threadsPerCore < 1 || threadsPerCore > 8) {
|
|
10233
|
+
throw new MetalProvisionError("invalid CPU pool threads-per-core topology");
|
|
10234
|
+
}
|
|
10235
|
+
return Array.from({ length: pool.physicalCores }, (_, core) => Array.from({ length: threadsPerCore }, (_2, thread) => cpus[core + thread * pool.physicalCores]));
|
|
10236
|
+
}
|
|
10211
10237
|
var guestNameFor = (computeKey) => `fzg-${createHash7("sha256").update(computeKey).digest("hex").slice(0, 16)}`;
|
|
10212
10238
|
var tapNameFor = (computeKey) => `fzt${createHash7("sha256").update(computeKey).digest("hex").slice(0, 12)}`;
|
|
10213
10239
|
var macForAddress = (address) => {
|
|
@@ -10265,6 +10291,7 @@ function validateMetalProfile(profile) {
|
|
|
10265
10291
|
if (!Number.isInteger(pool.physicalCores) || pool.physicalCores < 1 || pool.physicalCores > cpus.length) {
|
|
10266
10292
|
throw new MetalProvisionError("invalid CPU pool physical-core count");
|
|
10267
10293
|
}
|
|
10294
|
+
physicalCoreGroups(pool);
|
|
10268
10295
|
for (const cpu of cpus) {
|
|
10269
10296
|
if (assigned.has(cpu))
|
|
10270
10297
|
throw new MetalProvisionError("CPU pools overlap");
|
|
@@ -10334,26 +10361,38 @@ function allocateCpuPool(profile, claim, rows) {
|
|
|
10334
10361
|
const prior = rows.find((row2) => row2.computeKey === claim.computeKey);
|
|
10335
10362
|
if (prior) {
|
|
10336
10363
|
const retained = profile.cpuPools.find((pool) => pool.key === prior.cpuPoolKey);
|
|
10337
|
-
|
|
10364
|
+
const groups2 = retained ? physicalCoreGroups(retained) : [];
|
|
10365
|
+
const retainedCpus = new Set(membersOfLinuxList(prior.allowedCpus, "persisted guest CPU"));
|
|
10366
|
+
const matchedGroups = groups2.filter((group) => group.every((cpu) => retainedCpus.has(cpu)));
|
|
10367
|
+
if (!retained || retained.memoryNodes !== prior.allowedMemoryNodes || matchedGroups.length !== claim.spec.physicalCores || matchedGroups.flat().length !== retainedCpus.size || retainedCpus.size < claim.spec.vcpu || claim.spec.cpuPoolKey !== undefined && claim.spec.cpuPoolKey !== retained.key) {
|
|
10338
10368
|
throw new MetalProvisionError("persisted guest CPU pool no longer matches the host profile");
|
|
10339
10369
|
}
|
|
10340
|
-
return
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
throw new MetalProvisionError("requested CPU pool is unavailable");
|
|
10347
|
-
}
|
|
10348
|
-
if (requested.physicalCores < claim.spec.physicalCores || membersOfLinuxList(requested.cpus, "CPU").length < claim.spec.vcpu)
|
|
10349
|
-
throw new MetalProvisionError("requested CPU pool cannot satisfy this guest");
|
|
10350
|
-
return requested;
|
|
10370
|
+
return {
|
|
10371
|
+
key: retained.key,
|
|
10372
|
+
cpus: prior.allowedCpus,
|
|
10373
|
+
physicalCores: matchedGroups.length,
|
|
10374
|
+
memoryNodes: prior.allowedMemoryNodes
|
|
10375
|
+
};
|
|
10351
10376
|
}
|
|
10352
|
-
const
|
|
10377
|
+
const usedCpus = new Set(rows.flatMap((row2) => membersOfLinuxList(row2.allowedCpus, "persisted guest CPU")));
|
|
10378
|
+
const candidates = profile.cpuPools.filter((pool) => !claim.spec.cpuPoolKey || pool.key === claim.spec.cpuPoolKey).map((pool) => ({
|
|
10379
|
+
pool,
|
|
10380
|
+
available: physicalCoreGroups(pool).filter((group) => group.every((cpu) => !usedCpus.has(cpu)))
|
|
10381
|
+
})).filter(({ available }) => {
|
|
10382
|
+
const selected2 = available.slice(0, claim.spec.physicalCores);
|
|
10383
|
+
return selected2.length === claim.spec.physicalCores && selected2.flat().length >= claim.spec.vcpu;
|
|
10384
|
+
}).sort((left, right) => left.available.length - right.available.length || left.pool.key.localeCompare(right.pool.key));
|
|
10353
10385
|
const selected = candidates[0];
|
|
10354
|
-
if (!selected)
|
|
10355
|
-
throw new MetalProvisionError("no exclusive CPU
|
|
10356
|
-
|
|
10386
|
+
if (!selected) {
|
|
10387
|
+
throw new MetalProvisionError(claim.spec.cpuPoolKey ? "requested CPU pool cannot satisfy this guest" : "no exclusive CPU capacity can satisfy this guest");
|
|
10388
|
+
}
|
|
10389
|
+
const groups = selected.available.slice(0, claim.spec.physicalCores);
|
|
10390
|
+
return {
|
|
10391
|
+
key: selected.pool.key,
|
|
10392
|
+
cpus: linuxList(groups.flat()),
|
|
10393
|
+
physicalCores: groups.length,
|
|
10394
|
+
memoryNodes: selected.pool.memoryNodes
|
|
10395
|
+
};
|
|
10357
10396
|
}
|
|
10358
10397
|
var base64 = (value) => Buffer.from(value).toString("base64");
|
|
10359
10398
|
var yamlFile = (path2, content, permissions) => ` - path: ${JSON.stringify(path2)}
|
package/dist/fz.js
CHANGED
|
@@ -4810,8 +4810,8 @@ async function spawnWith(command, env, report = () => {}, options = {}) {
|
|
|
4810
4810
|
}
|
|
4811
4811
|
|
|
4812
4812
|
// src/cli/index.ts
|
|
4813
|
-
import { existsSync as existsSync12, lstatSync as lstatSync10, mkdirSync as mkdirSync13, readFileSync as
|
|
4814
|
-
import { basename as basename3, dirname as dirname14, isAbsolute as isAbsolute7, join as join12, resolve as
|
|
4813
|
+
import { existsSync as existsSync12, lstatSync as lstatSync10, mkdirSync as mkdirSync13, readFileSync as readFileSync16, writeFileSync as writeFileSync13 } from "fs";
|
|
4814
|
+
import { basename as basename3, dirname as dirname14, isAbsolute as isAbsolute7, join as join12, resolve as resolve12 } from "path";
|
|
4815
4815
|
|
|
4816
4816
|
// src/process-input.ts
|
|
4817
4817
|
async function writeAndCloseProcessInput(input, value) {
|
|
@@ -4837,7 +4837,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
4837
4837
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
4838
4838
|
|
|
4839
4839
|
// src/version.ts
|
|
4840
|
-
var VERSION2 = "0.1.
|
|
4840
|
+
var VERSION2 = "0.1.81";
|
|
4841
4841
|
|
|
4842
4842
|
// src/software.ts
|
|
4843
4843
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -15550,6 +15550,17 @@ function membersOfLinuxList(value, label) {
|
|
|
15550
15550
|
throw new MetalProvisionError(`${label} list overlaps itself`);
|
|
15551
15551
|
return members;
|
|
15552
15552
|
}
|
|
15553
|
+
function physicalCoreGroups(pool) {
|
|
15554
|
+
const cpus = membersOfLinuxList(pool.cpus, "CPU");
|
|
15555
|
+
if (cpus.length % pool.physicalCores !== 0) {
|
|
15556
|
+
throw new MetalProvisionError("CPU pool threads must divide evenly across physical cores");
|
|
15557
|
+
}
|
|
15558
|
+
const threadsPerCore = cpus.length / pool.physicalCores;
|
|
15559
|
+
if (!Number.isInteger(threadsPerCore) || threadsPerCore < 1 || threadsPerCore > 8) {
|
|
15560
|
+
throw new MetalProvisionError("invalid CPU pool threads-per-core topology");
|
|
15561
|
+
}
|
|
15562
|
+
return Array.from({ length: pool.physicalCores }, (_, core) => Array.from({ length: threadsPerCore }, (_2, thread) => cpus[core + thread * pool.physicalCores]));
|
|
15563
|
+
}
|
|
15553
15564
|
function validateMetalProfile(profile) {
|
|
15554
15565
|
if (!SAFE_NAME2.test(profile.volumeGroup))
|
|
15555
15566
|
throw new MetalProvisionError("invalid volume group");
|
|
@@ -15598,6 +15609,7 @@ function validateMetalProfile(profile) {
|
|
|
15598
15609
|
if (!Number.isInteger(pool.physicalCores) || pool.physicalCores < 1 || pool.physicalCores > cpus.length) {
|
|
15599
15610
|
throw new MetalProvisionError("invalid CPU pool physical-core count");
|
|
15600
15611
|
}
|
|
15612
|
+
physicalCoreGroups(pool);
|
|
15601
15613
|
for (const cpu of cpus) {
|
|
15602
15614
|
if (assigned.has(cpu))
|
|
15603
15615
|
throw new MetalProvisionError("CPU pools overlap");
|
|
@@ -17678,6 +17690,38 @@ async function runRepositoryOperation(operation, requestedRoot, args = []) {
|
|
|
17678
17690
|
`), { cwd: resolved.cwd });
|
|
17679
17691
|
}
|
|
17680
17692
|
|
|
17693
|
+
// src/cli/app-build.ts
|
|
17694
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
17695
|
+
import { resolve as resolve11 } from "path";
|
|
17696
|
+
function planAppBuild(input) {
|
|
17697
|
+
const cwd = resolve11(input.root);
|
|
17698
|
+
let manifest;
|
|
17699
|
+
try {
|
|
17700
|
+
manifest = JSON.parse(readFileSync15(resolve11(cwd, "package.json"), "utf8"));
|
|
17701
|
+
} catch {
|
|
17702
|
+
throw new Error(`--root must name a ForgeZero App checkout: ${cwd}`);
|
|
17703
|
+
}
|
|
17704
|
+
if (manifest.name !== "@forgezero/app") {
|
|
17705
|
+
throw new Error(`--root must name @forgezero/app, not ${manifest.name ?? "an unnamed package"}.`);
|
|
17706
|
+
}
|
|
17707
|
+
if (input.profile && !["local", "development", "production"].includes(input.profile)) {
|
|
17708
|
+
throw new Error("--profile must be local, development, or production.");
|
|
17709
|
+
}
|
|
17710
|
+
if (input.profile && input.api)
|
|
17711
|
+
throw new Error("Choose --profile or --api, not both.");
|
|
17712
|
+
return {
|
|
17713
|
+
cwd,
|
|
17714
|
+
command: [
|
|
17715
|
+
"bun",
|
|
17716
|
+
"run",
|
|
17717
|
+
"build",
|
|
17718
|
+
"--",
|
|
17719
|
+
...input.profile ? ["--profile", input.profile] : [],
|
|
17720
|
+
...input.api ? ["--api", input.api] : []
|
|
17721
|
+
]
|
|
17722
|
+
};
|
|
17723
|
+
}
|
|
17724
|
+
|
|
17681
17725
|
// src/cli/index.ts
|
|
17682
17726
|
var DEFAULT_MODE = (THRESHOLD_MODES.find((mode) => mode.threshold === 1 && mode.total === 1) ?? THRESHOLD_MODES[0]).id;
|
|
17683
17727
|
var PACKAGED_AGENT_BIN2 = fileURLToPath3(new URL("./fz-agent.js", import.meta.url));
|
|
@@ -17907,7 +17951,7 @@ async function api(options, path, init) {
|
|
|
17907
17951
|
return { status: response.status, body };
|
|
17908
17952
|
}
|
|
17909
17953
|
function sleep(ms) {
|
|
17910
|
-
return new Promise((
|
|
17954
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
17911
17955
|
}
|
|
17912
17956
|
function browserCommand(url) {
|
|
17913
17957
|
if (process.platform === "darwin")
|
|
@@ -18006,7 +18050,7 @@ function readOwnerOnlySecret(path, label) {
|
|
|
18006
18050
|
if (uid !== undefined && uid !== 0 && metadata.uid !== uid) {
|
|
18007
18051
|
throw new Error(`The ${label} file ${path} is not owned by the current user.`);
|
|
18008
18052
|
}
|
|
18009
|
-
const value =
|
|
18053
|
+
const value = readFileSync16(path, "utf8").trim();
|
|
18010
18054
|
if (!value)
|
|
18011
18055
|
throw new Error(`The ${label} file ${path} is empty.`);
|
|
18012
18056
|
return value;
|
|
@@ -18187,7 +18231,7 @@ async function cmdApi(options, args) {
|
|
|
18187
18231
|
let body = undefined;
|
|
18188
18232
|
if (options.data !== undefined && options.dataFile)
|
|
18189
18233
|
throw new Error("Use only one of --data or --data-file.");
|
|
18190
|
-
const encoded = options.data === "-" ? await Bun.stdin.text() : options.dataFile ?
|
|
18234
|
+
const encoded = options.data === "-" ? await Bun.stdin.text() : options.dataFile ? readFileSync16(options.dataFile, "utf8") : options.data;
|
|
18191
18235
|
if (encoded !== undefined)
|
|
18192
18236
|
body = JSON.parse(encoded);
|
|
18193
18237
|
const result = await api(options, `${url.pathname}${url.search}`, { method, body });
|
|
@@ -18366,7 +18410,7 @@ async function cmdAgent(options, args) {
|
|
|
18366
18410
|
out.line();
|
|
18367
18411
|
out.line(" Compatibility SSH deploy public key:");
|
|
18368
18412
|
out.line();
|
|
18369
|
-
out.line(` ${
|
|
18413
|
+
out.line(` ${readFileSync16(gitIdentity.gitPublicKeyPath, "utf8").trim()}`);
|
|
18370
18414
|
out.line();
|
|
18371
18415
|
}
|
|
18372
18416
|
return 0;
|
|
@@ -18476,7 +18520,7 @@ function interactiveMetalBootstrap() {
|
|
|
18476
18520
|
});
|
|
18477
18521
|
}
|
|
18478
18522
|
function writeBootstrapConfig(path, config) {
|
|
18479
|
-
if (!isAbsolute7(path) ||
|
|
18523
|
+
if (!isAbsolute7(path) || resolve12(path) !== path)
|
|
18480
18524
|
throw new Error("--output must be a canonical absolute path");
|
|
18481
18525
|
mkdirSync13(dirname14(path), { recursive: true, mode: 448 });
|
|
18482
18526
|
writeFileSync13(path, `${JSON.stringify(config, null, 2)}
|
|
@@ -18540,7 +18584,7 @@ async function interactiveCloudflareBootstrap() {
|
|
|
18540
18584
|
});
|
|
18541
18585
|
}
|
|
18542
18586
|
function genesisOutputDirectory(path) {
|
|
18543
|
-
if (!isAbsolute7(path) ||
|
|
18587
|
+
if (!isAbsolute7(path) || resolve12(path) !== path)
|
|
18544
18588
|
throw new Error("--output must be a canonical absolute directory");
|
|
18545
18589
|
if (!existsSync12(path))
|
|
18546
18590
|
mkdirSync13(path, { recursive: true, mode: 448 });
|
|
@@ -19132,7 +19176,7 @@ async function cmdUnlock(options) {
|
|
|
19132
19176
|
if (options.key || options.userExplicit) {
|
|
19133
19177
|
throw new Error("SSH/user unlock coordinates are not accepted. SSH keys cannot open a WebAuthn-PRF custody envelope; use --phrase-file or --phrase-stdin.");
|
|
19134
19178
|
}
|
|
19135
|
-
const phraseText = options.phraseFile ? readOwnerOnlySecret(options.phraseFile, "recovery phrase") : options.phraseStdin ?
|
|
19179
|
+
const phraseText = options.phraseFile ? readOwnerOnlySecret(options.phraseFile, "recovery phrase") : options.phraseStdin ? readFileSync16(0, "utf8").trim() : "";
|
|
19136
19180
|
const phrase = phraseText ? phraseText.split(/\s+/) : [];
|
|
19137
19181
|
if (phrase.length !== 24) {
|
|
19138
19182
|
throw new Error(`Recovery phrase must contain exactly 24 words; received ${phrase.length}. Use --phrase-file or --phrase-stdin.`);
|
|
@@ -19229,6 +19273,23 @@ function cmdProject(options, args) {
|
|
|
19229
19273
|
return 1;
|
|
19230
19274
|
}
|
|
19231
19275
|
}
|
|
19276
|
+
async function cmdApp(options, args) {
|
|
19277
|
+
if ((args[0] ?? "") !== "build" || args.length !== 1) {
|
|
19278
|
+
out.fail("Usage: fz app build [--root <app>] [--profile local|development|production | --api <origin>]");
|
|
19279
|
+
return 2;
|
|
19280
|
+
}
|
|
19281
|
+
try {
|
|
19282
|
+
const plan = planAppBuild({
|
|
19283
|
+
root: options.projectRoot,
|
|
19284
|
+
...options.deployProfile !== "app" ? { profile: options.deployProfile } : {},
|
|
19285
|
+
...options.apiExplicit ? { api: options.api } : {}
|
|
19286
|
+
});
|
|
19287
|
+
return await spawnWith(plan.command, Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined)), (line) => out.line(line), { cwd: plan.cwd });
|
|
19288
|
+
} catch (cause) {
|
|
19289
|
+
out.fail(cause instanceof Error ? cause.message : String(cause));
|
|
19290
|
+
return 1;
|
|
19291
|
+
}
|
|
19292
|
+
}
|
|
19232
19293
|
function repositoryOperationArguments(options, args) {
|
|
19233
19294
|
const unsupported = unsupportedRepositoryCliOption(process.argv.slice(2));
|
|
19234
19295
|
if (unsupported === "--root")
|
|
@@ -19348,7 +19409,7 @@ function projectFromCheckout(options) {
|
|
|
19348
19409
|
try {
|
|
19349
19410
|
return loadConfig({
|
|
19350
19411
|
cwd: options.projectRoot,
|
|
19351
|
-
readFile: (path) => existsSync12(path) ?
|
|
19412
|
+
readFile: (path) => existsSync12(path) ? readFileSync16(path, "utf8") : undefined
|
|
19352
19413
|
}).config.project;
|
|
19353
19414
|
} catch (cause) {
|
|
19354
19415
|
throw new Error(`No --project was given and the checkout has no usable .fz/config.json: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
@@ -19411,7 +19472,7 @@ async function cmdDeploy(options, args) {
|
|
|
19411
19472
|
out.ok(`Initialized legacy .fz/deploy.json (${created.summary.digest}).`);
|
|
19412
19473
|
return 0;
|
|
19413
19474
|
}
|
|
19414
|
-
initializeTypeScriptDeployment(options.projectRoot, { name: options.projectName ?? basename3(
|
|
19475
|
+
initializeTypeScriptDeployment(options.projectRoot, { name: options.projectName ?? basename3(resolve12(options.projectRoot)), force: options.force });
|
|
19415
19476
|
const compiled = await compileDeploymentProject(options.projectRoot);
|
|
19416
19477
|
if (options.json)
|
|
19417
19478
|
out.line(JSON.stringify({ source: compiled.source, output: compiled.output, digest: compiled.digest }, null, 2));
|
|
@@ -19436,7 +19497,7 @@ async function cmdDeploy(options, args) {
|
|
|
19436
19497
|
return problems.length === 0 ? 0 : 1;
|
|
19437
19498
|
}
|
|
19438
19499
|
if (operation === "check" || operation === "sync") {
|
|
19439
|
-
if (existsSync12(
|
|
19500
|
+
if (existsSync12(resolve12(options.projectRoot, DEPLOY_SOURCE_FILE))) {
|
|
19440
19501
|
const expected = await compileDeploymentProject(options.projectRoot, { write: false });
|
|
19441
19502
|
const actual = inspectCompiledDeployment(options.projectRoot);
|
|
19442
19503
|
const current2 = canonicalJson(expected.plan) === canonicalJson(actual.plan);
|
|
@@ -19512,7 +19573,7 @@ async function cmdDeploy(options, args) {
|
|
|
19512
19573
|
branch: options.branch,
|
|
19513
19574
|
cloneUrl: required(options.cloneUrl, "--clone-url"),
|
|
19514
19575
|
sourceAuth,
|
|
19515
|
-
...options.knownHostsFile ? { knownHosts:
|
|
19576
|
+
...options.knownHostsFile ? { knownHosts: readFileSync16(options.knownHostsFile, "utf8") } : {},
|
|
19516
19577
|
projectKey
|
|
19517
19578
|
} });
|
|
19518
19579
|
const pipelineKey2 = String(created.pipelineKey);
|
|
@@ -19660,7 +19721,9 @@ function usage() {
|
|
|
19660
19721
|
fz bootstrap repair Reapply an explicitly supplied reviewed config
|
|
19661
19722
|
fz project init Create vendor-neutral, Git-persisted AI context
|
|
19662
19723
|
fz project sync Regenerate Claude/Codex/Gemini/Copilot/Cursor adapters
|
|
19663
|
-
|
|
19724
|
+
fz project check Fail when truth sources or generated adapters drift
|
|
19725
|
+
fz app build Build the static App for a numbered local/development/
|
|
19726
|
+
production choice, --profile, or an explicit --api origin
|
|
19664
19727
|
fz deploy init Create forgezero.deploy.ts and its fail-safe canonical plan
|
|
19665
19728
|
fz deploy compile Compile TypeScript into .fz/deploy.plan.json
|
|
19666
19729
|
fz deploy check Validate source, plan, actions, providers and topology
|
|
@@ -19810,6 +19873,9 @@ async function runCli() {
|
|
|
19810
19873
|
case "project":
|
|
19811
19874
|
code = cmdProject(options, args);
|
|
19812
19875
|
break;
|
|
19876
|
+
case "app":
|
|
19877
|
+
code = await cmdApp(options, args);
|
|
19878
|
+
break;
|
|
19813
19879
|
case "deploy":
|
|
19814
19880
|
code = await cmdDeploy(options, args);
|
|
19815
19881
|
break;
|
package/dist/metal-bootstrap.js
CHANGED
|
@@ -118,6 +118,17 @@ function membersOfLinuxList(value, label) {
|
|
|
118
118
|
throw new MetalProvisionError(`${label} list overlaps itself`);
|
|
119
119
|
return members;
|
|
120
120
|
}
|
|
121
|
+
function physicalCoreGroups(pool) {
|
|
122
|
+
const cpus = membersOfLinuxList(pool.cpus, "CPU");
|
|
123
|
+
if (cpus.length % pool.physicalCores !== 0) {
|
|
124
|
+
throw new MetalProvisionError("CPU pool threads must divide evenly across physical cores");
|
|
125
|
+
}
|
|
126
|
+
const threadsPerCore = cpus.length / pool.physicalCores;
|
|
127
|
+
if (!Number.isInteger(threadsPerCore) || threadsPerCore < 1 || threadsPerCore > 8) {
|
|
128
|
+
throw new MetalProvisionError("invalid CPU pool threads-per-core topology");
|
|
129
|
+
}
|
|
130
|
+
return Array.from({ length: pool.physicalCores }, (_, core) => Array.from({ length: threadsPerCore }, (_2, thread) => cpus[core + thread * pool.physicalCores]));
|
|
131
|
+
}
|
|
121
132
|
function validateMetalProfile(profile) {
|
|
122
133
|
if (!SAFE_NAME.test(profile.volumeGroup))
|
|
123
134
|
throw new MetalProvisionError("invalid volume group");
|
|
@@ -166,6 +177,7 @@ function validateMetalProfile(profile) {
|
|
|
166
177
|
if (!Number.isInteger(pool.physicalCores) || pool.physicalCores < 1 || pool.physicalCores > cpus.length) {
|
|
167
178
|
throw new MetalProvisionError("invalid CPU pool physical-core count");
|
|
168
179
|
}
|
|
180
|
+
physicalCoreGroups(pool);
|
|
169
181
|
for (const cpu of cpus) {
|
|
170
182
|
if (assigned.has(cpu))
|
|
171
183
|
throw new MetalProvisionError("CPU pools overlap");
|
|
@@ -354,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
|
|
|
354
366
|
}
|
|
355
367
|
|
|
356
368
|
// src/version.ts
|
|
357
|
-
var VERSION = "0.1.
|
|
369
|
+
var VERSION = "0.1.81";
|
|
358
370
|
|
|
359
371
|
// src/otel-collector.ts
|
|
360
372
|
var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
|
|
@@ -748,6 +748,32 @@ function membersOfLinuxList(value, label) {
|
|
|
748
748
|
throw new MetalProvisionError(`${label} list overlaps itself`);
|
|
749
749
|
return members;
|
|
750
750
|
}
|
|
751
|
+
function linuxList(members) {
|
|
752
|
+
const sorted = [...new Set(members)].sort((left, right) => left - right);
|
|
753
|
+
const ranges = [];
|
|
754
|
+
for (let index = 0;index < sorted.length; ) {
|
|
755
|
+
const start = sorted[index];
|
|
756
|
+
let end = start;
|
|
757
|
+
while (index + 1 < sorted.length && sorted[index + 1] === end + 1) {
|
|
758
|
+
index += 1;
|
|
759
|
+
end = sorted[index];
|
|
760
|
+
}
|
|
761
|
+
ranges.push(start === end ? String(start) : `${start}-${end}`);
|
|
762
|
+
index += 1;
|
|
763
|
+
}
|
|
764
|
+
return ranges.join(",");
|
|
765
|
+
}
|
|
766
|
+
function physicalCoreGroups(pool) {
|
|
767
|
+
const cpus = membersOfLinuxList(pool.cpus, "CPU");
|
|
768
|
+
if (cpus.length % pool.physicalCores !== 0) {
|
|
769
|
+
throw new MetalProvisionError("CPU pool threads must divide evenly across physical cores");
|
|
770
|
+
}
|
|
771
|
+
const threadsPerCore = cpus.length / pool.physicalCores;
|
|
772
|
+
if (!Number.isInteger(threadsPerCore) || threadsPerCore < 1 || threadsPerCore > 8) {
|
|
773
|
+
throw new MetalProvisionError("invalid CPU pool threads-per-core topology");
|
|
774
|
+
}
|
|
775
|
+
return Array.from({ length: pool.physicalCores }, (_, core) => Array.from({ length: threadsPerCore }, (_2, thread) => cpus[core + thread * pool.physicalCores]));
|
|
776
|
+
}
|
|
751
777
|
var guestNameFor = (computeKey) => `fzg-${createHash2("sha256").update(computeKey).digest("hex").slice(0, 16)}`;
|
|
752
778
|
var tapNameFor = (computeKey) => `fzt${createHash2("sha256").update(computeKey).digest("hex").slice(0, 12)}`;
|
|
753
779
|
var macForAddress = (address) => {
|
|
@@ -805,6 +831,7 @@ function validateMetalProfile(profile) {
|
|
|
805
831
|
if (!Number.isInteger(pool.physicalCores) || pool.physicalCores < 1 || pool.physicalCores > cpus.length) {
|
|
806
832
|
throw new MetalProvisionError("invalid CPU pool physical-core count");
|
|
807
833
|
}
|
|
834
|
+
physicalCoreGroups(pool);
|
|
808
835
|
for (const cpu of cpus) {
|
|
809
836
|
if (assigned.has(cpu))
|
|
810
837
|
throw new MetalProvisionError("CPU pools overlap");
|
|
@@ -874,26 +901,38 @@ function allocateCpuPool(profile, claim, rows) {
|
|
|
874
901
|
const prior = rows.find((row) => row.computeKey === claim.computeKey);
|
|
875
902
|
if (prior) {
|
|
876
903
|
const retained = profile.cpuPools.find((pool) => pool.key === prior.cpuPoolKey);
|
|
877
|
-
|
|
904
|
+
const groups2 = retained ? physicalCoreGroups(retained) : [];
|
|
905
|
+
const retainedCpus = new Set(membersOfLinuxList(prior.allowedCpus, "persisted guest CPU"));
|
|
906
|
+
const matchedGroups = groups2.filter((group) => group.every((cpu) => retainedCpus.has(cpu)));
|
|
907
|
+
if (!retained || retained.memoryNodes !== prior.allowedMemoryNodes || matchedGroups.length !== claim.spec.physicalCores || matchedGroups.flat().length !== retainedCpus.size || retainedCpus.size < claim.spec.vcpu || claim.spec.cpuPoolKey !== undefined && claim.spec.cpuPoolKey !== retained.key) {
|
|
878
908
|
throw new MetalProvisionError("persisted guest CPU pool no longer matches the host profile");
|
|
879
909
|
}
|
|
880
|
-
return
|
|
910
|
+
return {
|
|
911
|
+
key: retained.key,
|
|
912
|
+
cpus: prior.allowedCpus,
|
|
913
|
+
physicalCores: matchedGroups.length,
|
|
914
|
+
memoryNodes: prior.allowedMemoryNodes
|
|
915
|
+
};
|
|
881
916
|
}
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
return requested;
|
|
891
|
-
}
|
|
892
|
-
const candidates = profile.cpuPools.filter((pool) => !used.has(pool.key) && pool.physicalCores >= claim.spec.physicalCores && membersOfLinuxList(pool.cpus, "CPU").length >= claim.spec.vcpu).sort((left, right) => left.physicalCores - right.physicalCores || membersOfLinuxList(left.cpus, "CPU").length - membersOfLinuxList(right.cpus, "CPU").length || left.key.localeCompare(right.key));
|
|
917
|
+
const usedCpus = new Set(rows.flatMap((row) => membersOfLinuxList(row.allowedCpus, "persisted guest CPU")));
|
|
918
|
+
const candidates = profile.cpuPools.filter((pool) => !claim.spec.cpuPoolKey || pool.key === claim.spec.cpuPoolKey).map((pool) => ({
|
|
919
|
+
pool,
|
|
920
|
+
available: physicalCoreGroups(pool).filter((group) => group.every((cpu) => !usedCpus.has(cpu)))
|
|
921
|
+
})).filter(({ available }) => {
|
|
922
|
+
const selected2 = available.slice(0, claim.spec.physicalCores);
|
|
923
|
+
return selected2.length === claim.spec.physicalCores && selected2.flat().length >= claim.spec.vcpu;
|
|
924
|
+
}).sort((left, right) => left.available.length - right.available.length || left.pool.key.localeCompare(right.pool.key));
|
|
893
925
|
const selected = candidates[0];
|
|
894
|
-
if (!selected)
|
|
895
|
-
throw new MetalProvisionError("no exclusive CPU
|
|
896
|
-
|
|
926
|
+
if (!selected) {
|
|
927
|
+
throw new MetalProvisionError(claim.spec.cpuPoolKey ? "requested CPU pool cannot satisfy this guest" : "no exclusive CPU capacity can satisfy this guest");
|
|
928
|
+
}
|
|
929
|
+
const groups = selected.available.slice(0, claim.spec.physicalCores);
|
|
930
|
+
return {
|
|
931
|
+
key: selected.pool.key,
|
|
932
|
+
cpus: linuxList(groups.flat()),
|
|
933
|
+
physicalCores: groups.length,
|
|
934
|
+
memoryNodes: selected.pool.memoryNodes
|
|
935
|
+
};
|
|
897
936
|
}
|
|
898
937
|
var base64 = (value) => Buffer.from(value).toString("base64");
|
|
899
938
|
var yamlFile = (path2, content, permissions) => ` - path: ${JSON.stringify(path2)}
|
|
@@ -3,7 +3,7 @@ export interface MetalImage {
|
|
|
3
3
|
path: string;
|
|
4
4
|
sha256: string;
|
|
5
5
|
}
|
|
6
|
-
/** One non-overlapping
|
|
6
|
+
/** One non-overlapping NUMA-local CPU capacity pool that may serve several guests. */
|
|
7
7
|
export interface MetalCpuPool {
|
|
8
8
|
key: string;
|
|
9
9
|
/** Linux CPU-list syntax, including every SMT sibling in this pool. */
|
|
@@ -13,6 +13,9 @@ export interface MetalCpuPool {
|
|
|
13
13
|
/** Linux NUMA node-list syntax. Optional on a non-NUMA machine. */
|
|
14
14
|
memoryNodes?: string;
|
|
15
15
|
}
|
|
16
|
+
/** Exact non-overlapping physical-core slice reserved for one guest. */
|
|
17
|
+
export interface MetalCpuAllocation extends MetalCpuPool {
|
|
18
|
+
}
|
|
16
19
|
export interface MetalProvisionProfile {
|
|
17
20
|
volumeGroup: string;
|
|
18
21
|
bridge: string;
|
|
@@ -70,8 +73,8 @@ export declare const tapNameFor: (computeKey: string) => string;
|
|
|
70
73
|
export declare const macForAddress: (address: string) => string;
|
|
71
74
|
export declare function validateMetalProfile(profile: MetalProvisionProfile): void;
|
|
72
75
|
export declare function allocateAddress(profile: MetalProvisionProfile, computeKey: string, rows: GuestManifest[]): string;
|
|
73
|
-
/** Stable tight-fit allocation of one
|
|
74
|
-
export declare function allocateCpuPool(profile: MetalProvisionProfile, claim: Pick<RemoteProvisionClaim, 'computeKey' | 'spec'>, rows: GuestManifest[]):
|
|
76
|
+
/** Stable tight-fit allocation of exact physical cores inside one NUMA-local pool. */
|
|
77
|
+
export declare function allocateCpuPool(profile: MetalProvisionProfile, claim: Pick<RemoteProvisionClaim, 'computeKey' | 'spec'>, rows: GuestManifest[]): MetalCpuAllocation;
|
|
75
78
|
export declare function guestBootstrapOperations(profile: MetalProvisionProfile, attested?: boolean, hasEnrolment?: boolean, nodeLabel?: string): readonly (readonly string[])[];
|
|
76
79
|
export declare function cloudInit(profile: MetalProvisionProfile, claim: CreateRemoteProvisionClaim, manifest: GuestManifest): {
|
|
77
80
|
userData: string;
|
package/dist/metal-provision.js
CHANGED
|
@@ -748,6 +748,32 @@ function membersOfLinuxList(value, label) {
|
|
|
748
748
|
throw new MetalProvisionError(`${label} list overlaps itself`);
|
|
749
749
|
return members;
|
|
750
750
|
}
|
|
751
|
+
function linuxList(members) {
|
|
752
|
+
const sorted = [...new Set(members)].sort((left, right) => left - right);
|
|
753
|
+
const ranges = [];
|
|
754
|
+
for (let index = 0;index < sorted.length; ) {
|
|
755
|
+
const start = sorted[index];
|
|
756
|
+
let end = start;
|
|
757
|
+
while (index + 1 < sorted.length && sorted[index + 1] === end + 1) {
|
|
758
|
+
index += 1;
|
|
759
|
+
end = sorted[index];
|
|
760
|
+
}
|
|
761
|
+
ranges.push(start === end ? String(start) : `${start}-${end}`);
|
|
762
|
+
index += 1;
|
|
763
|
+
}
|
|
764
|
+
return ranges.join(",");
|
|
765
|
+
}
|
|
766
|
+
function physicalCoreGroups(pool) {
|
|
767
|
+
const cpus = membersOfLinuxList(pool.cpus, "CPU");
|
|
768
|
+
if (cpus.length % pool.physicalCores !== 0) {
|
|
769
|
+
throw new MetalProvisionError("CPU pool threads must divide evenly across physical cores");
|
|
770
|
+
}
|
|
771
|
+
const threadsPerCore = cpus.length / pool.physicalCores;
|
|
772
|
+
if (!Number.isInteger(threadsPerCore) || threadsPerCore < 1 || threadsPerCore > 8) {
|
|
773
|
+
throw new MetalProvisionError("invalid CPU pool threads-per-core topology");
|
|
774
|
+
}
|
|
775
|
+
return Array.from({ length: pool.physicalCores }, (_, core) => Array.from({ length: threadsPerCore }, (_2, thread) => cpus[core + thread * pool.physicalCores]));
|
|
776
|
+
}
|
|
751
777
|
var guestNameFor = (computeKey) => `fzg-${createHash2("sha256").update(computeKey).digest("hex").slice(0, 16)}`;
|
|
752
778
|
var tapNameFor = (computeKey) => `fzt${createHash2("sha256").update(computeKey).digest("hex").slice(0, 12)}`;
|
|
753
779
|
var macForAddress = (address) => {
|
|
@@ -805,6 +831,7 @@ function validateMetalProfile(profile) {
|
|
|
805
831
|
if (!Number.isInteger(pool.physicalCores) || pool.physicalCores < 1 || pool.physicalCores > cpus.length) {
|
|
806
832
|
throw new MetalProvisionError("invalid CPU pool physical-core count");
|
|
807
833
|
}
|
|
834
|
+
physicalCoreGroups(pool);
|
|
808
835
|
for (const cpu of cpus) {
|
|
809
836
|
if (assigned.has(cpu))
|
|
810
837
|
throw new MetalProvisionError("CPU pools overlap");
|
|
@@ -874,26 +901,38 @@ function allocateCpuPool(profile, claim, rows) {
|
|
|
874
901
|
const prior = rows.find((row) => row.computeKey === claim.computeKey);
|
|
875
902
|
if (prior) {
|
|
876
903
|
const retained = profile.cpuPools.find((pool) => pool.key === prior.cpuPoolKey);
|
|
877
|
-
|
|
904
|
+
const groups2 = retained ? physicalCoreGroups(retained) : [];
|
|
905
|
+
const retainedCpus = new Set(membersOfLinuxList(prior.allowedCpus, "persisted guest CPU"));
|
|
906
|
+
const matchedGroups = groups2.filter((group) => group.every((cpu) => retainedCpus.has(cpu)));
|
|
907
|
+
if (!retained || retained.memoryNodes !== prior.allowedMemoryNodes || matchedGroups.length !== claim.spec.physicalCores || matchedGroups.flat().length !== retainedCpus.size || retainedCpus.size < claim.spec.vcpu || claim.spec.cpuPoolKey !== undefined && claim.spec.cpuPoolKey !== retained.key) {
|
|
878
908
|
throw new MetalProvisionError("persisted guest CPU pool no longer matches the host profile");
|
|
879
909
|
}
|
|
880
|
-
return
|
|
910
|
+
return {
|
|
911
|
+
key: retained.key,
|
|
912
|
+
cpus: prior.allowedCpus,
|
|
913
|
+
physicalCores: matchedGroups.length,
|
|
914
|
+
memoryNodes: prior.allowedMemoryNodes
|
|
915
|
+
};
|
|
881
916
|
}
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
return requested;
|
|
891
|
-
}
|
|
892
|
-
const candidates = profile.cpuPools.filter((pool) => !used.has(pool.key) && pool.physicalCores >= claim.spec.physicalCores && membersOfLinuxList(pool.cpus, "CPU").length >= claim.spec.vcpu).sort((left, right) => left.physicalCores - right.physicalCores || membersOfLinuxList(left.cpus, "CPU").length - membersOfLinuxList(right.cpus, "CPU").length || left.key.localeCompare(right.key));
|
|
917
|
+
const usedCpus = new Set(rows.flatMap((row) => membersOfLinuxList(row.allowedCpus, "persisted guest CPU")));
|
|
918
|
+
const candidates = profile.cpuPools.filter((pool) => !claim.spec.cpuPoolKey || pool.key === claim.spec.cpuPoolKey).map((pool) => ({
|
|
919
|
+
pool,
|
|
920
|
+
available: physicalCoreGroups(pool).filter((group) => group.every((cpu) => !usedCpus.has(cpu)))
|
|
921
|
+
})).filter(({ available }) => {
|
|
922
|
+
const selected2 = available.slice(0, claim.spec.physicalCores);
|
|
923
|
+
return selected2.length === claim.spec.physicalCores && selected2.flat().length >= claim.spec.vcpu;
|
|
924
|
+
}).sort((left, right) => left.available.length - right.available.length || left.pool.key.localeCompare(right.pool.key));
|
|
893
925
|
const selected = candidates[0];
|
|
894
|
-
if (!selected)
|
|
895
|
-
throw new MetalProvisionError("no exclusive CPU
|
|
896
|
-
|
|
926
|
+
if (!selected) {
|
|
927
|
+
throw new MetalProvisionError(claim.spec.cpuPoolKey ? "requested CPU pool cannot satisfy this guest" : "no exclusive CPU capacity can satisfy this guest");
|
|
928
|
+
}
|
|
929
|
+
const groups = selected.available.slice(0, claim.spec.physicalCores);
|
|
930
|
+
return {
|
|
931
|
+
key: selected.pool.key,
|
|
932
|
+
cpus: linuxList(groups.flat()),
|
|
933
|
+
physicalCores: groups.length,
|
|
934
|
+
memoryNodes: selected.pool.memoryNodes
|
|
935
|
+
};
|
|
897
936
|
}
|
|
898
937
|
var base64 = (value) => Buffer.from(value).toString("base64");
|
|
899
938
|
var yamlFile = (path2, content, permissions) => ` - path: ${JSON.stringify(path2)}
|
|
@@ -1416,7 +1416,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
1416
1416
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
1417
1417
|
|
|
1418
1418
|
// src/version.ts
|
|
1419
|
-
var VERSION = "0.1.
|
|
1419
|
+
var VERSION = "0.1.81";
|
|
1420
1420
|
|
|
1421
1421
|
// src/software.ts
|
|
1422
1422
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -5027,6 +5027,17 @@ function membersOfLinuxList(value, label) {
|
|
|
5027
5027
|
throw new MetalProvisionError(`${label} list overlaps itself`);
|
|
5028
5028
|
return members;
|
|
5029
5029
|
}
|
|
5030
|
+
function physicalCoreGroups(pool) {
|
|
5031
|
+
const cpus = membersOfLinuxList(pool.cpus, "CPU");
|
|
5032
|
+
if (cpus.length % pool.physicalCores !== 0) {
|
|
5033
|
+
throw new MetalProvisionError("CPU pool threads must divide evenly across physical cores");
|
|
5034
|
+
}
|
|
5035
|
+
const threadsPerCore = cpus.length / pool.physicalCores;
|
|
5036
|
+
if (!Number.isInteger(threadsPerCore) || threadsPerCore < 1 || threadsPerCore > 8) {
|
|
5037
|
+
throw new MetalProvisionError("invalid CPU pool threads-per-core topology");
|
|
5038
|
+
}
|
|
5039
|
+
return Array.from({ length: pool.physicalCores }, (_, core) => Array.from({ length: threadsPerCore }, (_2, thread) => cpus[core + thread * pool.physicalCores]));
|
|
5040
|
+
}
|
|
5030
5041
|
function validateMetalProfile(profile) {
|
|
5031
5042
|
if (!SAFE_NAME2.test(profile.volumeGroup))
|
|
5032
5043
|
throw new MetalProvisionError("invalid volume group");
|
|
@@ -5075,6 +5086,7 @@ function validateMetalProfile(profile) {
|
|
|
5075
5086
|
if (!Number.isInteger(pool.physicalCores) || pool.physicalCores < 1 || pool.physicalCores > cpus.length) {
|
|
5076
5087
|
throw new MetalProvisionError("invalid CPU pool physical-core count");
|
|
5077
5088
|
}
|
|
5089
|
+
physicalCoreGroups(pool);
|
|
5078
5090
|
for (const cpu of cpus) {
|
|
5079
5091
|
if (assigned.has(cpu))
|
|
5080
5092
|
throw new MetalProvisionError("CPU pools overlap");
|
|
@@ -2647,7 +2647,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
2647
2647
|
}
|
|
2648
2648
|
|
|
2649
2649
|
// src/version.ts
|
|
2650
|
-
var VERSION3 = "0.1.
|
|
2650
|
+
var VERSION3 = "0.1.81";
|
|
2651
2651
|
|
|
2652
2652
|
// src/egress-policy.ts
|
|
2653
2653
|
import { realpathSync as realpathSync3 } from "node:fs";
|
package/dist/provision.js
CHANGED
|
@@ -2647,7 +2647,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
2647
2647
|
}
|
|
2648
2648
|
|
|
2649
2649
|
// src/version.ts
|
|
2650
|
-
var VERSION3 = "0.1.
|
|
2650
|
+
var VERSION3 = "0.1.81";
|
|
2651
2651
|
|
|
2652
2652
|
// src/egress-policy.ts
|
|
2653
2653
|
import { realpathSync as realpathSync3 } from "node:fs";
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** One package version shared by both public binaries. Pinned to package.json by tests. */
|
|
2
|
-
export declare const VERSION = "0.1.
|
|
2
|
+
export declare const VERSION = "0.1.81";
|