@forgezero/agent 0.1.101 → 0.1.103

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.
@@ -1386,7 +1386,7 @@ async function buildBootstrapBundle(input, exec = run) {
1386
1386
  }
1387
1387
 
1388
1388
  // src/bootstrap.ts
1389
- import { createHash as createHash2, createHmac as createHmac2, randomBytes as randomBytes3 } from "crypto";
1389
+ import { createHash as createHash2, createHmac as createHmac2, createPrivateKey, randomBytes as randomBytes3 } from "crypto";
1390
1390
  import {
1391
1391
  chmodSync as chmodSync3,
1392
1392
  existsSync as existsSync5,
@@ -1420,7 +1420,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1420
1420
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1421
1421
 
1422
1422
  // src/version.ts
1423
- var VERSION = "0.1.101";
1423
+ var VERSION = "0.1.103";
1424
1424
 
1425
1425
  // src/software.ts
1426
1426
  var PINNED_BUN_VERSION = "1.3.14";
@@ -3090,10 +3090,16 @@ var httpsOrigin = (name, raw) => {
3090
3090
  };
3091
3091
  var agentTelemetryOrigin = (raw) => raw === "http://127.0.0.1:4318" ? raw : httpsOrigin("agentOtlpEndpoint", raw);
3092
3092
  function validatePlatformInitialInventory(value) {
3093
- if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["metalHostname", "region", "computes", "attestation", "deployment"].includes(key)) || !/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(value.metalHostname) || !value.region || typeof value.region !== "object" || Array.isArray(value.region) || Object.keys(value.region).some((key) => !["key", "label", "country", "city", "confidentialCapable"].includes(key)) || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.region.key) || !/^[A-Z]{2}$/.test(value.region.country) || value.region.confidentialCapable !== true || !Array.isArray(value.computes)) {
3093
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["metalHostname", "metalIdentity", "region", "computes", "attestation", "deployment"].includes(key)) || !/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(value.metalHostname) || !value.region || typeof value.region !== "object" || Array.isArray(value.region) || Object.keys(value.region).some((key) => !["key", "label", "country", "city", "confidentialCapable"].includes(key)) || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.region.key) || !/^[A-Z]{2}$/.test(value.region.country) || value.region.confidentialCapable !== true || !Array.isArray(value.computes)) {
3094
3094
  throw new Error("initial platform inventory coordinates are invalid");
3095
3095
  }
3096
3096
  safeAtom("initialInventory.region.label", value.region.label);
3097
+ if (value.metalIdentity !== undefined) {
3098
+ const identity = value.metalIdentity;
3099
+ if (!identity || typeof identity !== "object" || Array.isArray(identity) || Object.keys(identity).some((key) => !["nodeKey", "publicKeys"].includes(key)) || !/^[A-Za-z0-9_-]{43}$/.test(identity.nodeKey) || !identity.publicKeys || identity.publicKeys.ed25519 !== identity.nodeKey || !/^[A-Za-z0-9_-]{1,8192}$/.test(identity.publicKeys.mlDsa) || Object.keys(identity.publicKeys).some((key) => !["ed25519", "mlDsa"].includes(key))) {
3100
+ throw new Error("initial platform Metal identity is invalid");
3101
+ }
3102
+ }
3097
3103
  if (value.region.city !== undefined)
3098
3104
  safeAtom("initialInventory.region.city", value.region.city);
3099
3105
  if (value.deployment !== undefined && (!value.deployment || typeof value.deployment !== "object" || Array.isArray(value.deployment) || Object.keys(value.deployment).some((key) => !["source", "branch", "revision", "bundleSha256"].includes(key)) || value.deployment.source !== "bootstrap-bundle" || !["dev", "main"].includes(value.deployment.branch) || !/^[a-f0-9]{40}$/.test(value.deployment.revision) || !/^[a-f0-9]{64}$/.test(value.deployment.bundleSha256)))
@@ -3128,6 +3134,7 @@ function validatePlatformInitialInventory(value) {
3128
3134
  }
3129
3135
  return {
3130
3136
  metalHostname: value.metalHostname,
3137
+ ...value.metalIdentity ? { metalIdentity: structuredClone(value.metalIdentity) } : {},
3131
3138
  region: { ...value.region },
3132
3139
  computes: value.computes.map((compute, index) => ({
3133
3140
  ...computes[index],
@@ -3181,6 +3188,11 @@ function validatePlatformSharedEnvironment(input) {
3181
3188
  } else if (input.email !== undefined) {
3182
3189
  throw new Error("Bootstrap email provider must be smtp or jetemail.");
3183
3190
  }
3191
+ if (input.githubApp) {
3192
+ if (!/^(?:Iv1\.[A-Fa-f0-9]{16}|Ov23li[A-Za-z0-9]{14,})$/.test(input.githubApp.clientId) || !/^[1-9][0-9]{0,19}$/.test(input.githubApp.appId) || !/^[a-z0-9][a-z0-9-]{0,99}$/.test(input.githubApp.slug)) {
3193
+ throw new Error("GitHub App client id, app id or slug is malformed.");
3194
+ }
3195
+ }
3184
3196
  boundedInteger("publicApiPort", input.publicApiPort, 1024, 65533);
3185
3197
  if (!Array.isArray(input.seedSyncMembers) || input.seedSyncMembers.length < 3 || input.seedSyncMembers.length > 64 || new Set(input.seedSyncMembers).size !== input.seedSyncMembers.length) {
3186
3198
  throw new Error("seedSyncMembers must contain 3 to 64 unique physical host identities.");
@@ -3311,6 +3323,9 @@ function renderPlatformSharedEnvironment(input) {
3311
3323
  FZ_REALTIME_WORKER_SCRIPT: value.realtime?.workerScriptName ?? "",
3312
3324
  FZ_REALTIME_ENDPOINT: value.realtime?.endpoint ?? "",
3313
3325
  FZ_REALTIME_PRODUCER: value.realtime?.producer ?? "",
3326
+ FZ_GITHUB_CLIENT_ID: value.githubApp?.clientId ?? "",
3327
+ FZ_GITHUB_APP_ID: value.githubApp?.appId ?? "",
3328
+ FZ_GITHUB_APP_SLUG: value.githubApp?.slug ?? "",
3314
3329
  FZ_PLATFORM_INITIAL_INVENTORY: value.initialInventory ? JSON.stringify(value.initialInventory) : ""
3315
3330
  };
3316
3331
  return `# Generated by fz bootstrap platform. Non-secret coordinates only.
@@ -3322,6 +3337,9 @@ function platformApiCredentialSpecs(options) {
3322
3337
  const optional = [
3323
3338
  ["fz_smtp.password", options.emailProvider === "smtp"],
3324
3339
  ["fz_jetemail.apiKey", options.emailProvider === "jetemail"],
3340
+ ["fz_github.clientSecret", options.githubApp],
3341
+ ["fz_github.privateKey", options.githubApp],
3342
+ ["fz_github.webhookSecret", options.githubApp],
3325
3343
  ["CF_API_TOKEN", options.cloudflareKv],
3326
3344
  ["CF_TUNNEL_TOKEN", options.cloudflareKv],
3327
3345
  ["REALTIME_PUBLISH_SECRET", options.realtime],
@@ -3717,7 +3735,17 @@ function validatePlatformBootstrapSecrets(config, input) {
3717
3735
  if (!input || typeof input !== "object" || Array.isArray(input))
3718
3736
  throw new Error("bootstrap credential input must be an object");
3719
3737
  const source = input;
3720
- const allowed = ["clusterBootstrapCode", "emailSecret", "enrolmentToken", "backupS3Secret", "cloudflareTunnelToken", "cloudflareApiToken"];
3738
+ const allowed = [
3739
+ "clusterBootstrapCode",
3740
+ "emailSecret",
3741
+ "enrolmentToken",
3742
+ "backupS3Secret",
3743
+ "cloudflareTunnelToken",
3744
+ "cloudflareApiToken",
3745
+ "githubClientSecret",
3746
+ "githubPrivateKeyBase64",
3747
+ "githubWebhookSecret"
3748
+ ];
3721
3749
  const unknown = Object.keys(source).filter((key) => !allowed.includes(key));
3722
3750
  if (unknown.length)
3723
3751
  throw new Error(`bootstrap credential input contains unsupported field ${unknown[0]}`);
@@ -3727,6 +3755,9 @@ function validatePlatformBootstrapSecrets(config, input) {
3727
3755
  const backupS3Secret = typeof source.backupS3Secret === "string" ? source.backupS3Secret.trim() : undefined;
3728
3756
  const cloudflareTunnelToken = typeof source.cloudflareTunnelToken === "string" ? source.cloudflareTunnelToken.trim() : undefined;
3729
3757
  const cloudflareApiToken = typeof source.cloudflareApiToken === "string" ? source.cloudflareApiToken.trim() : undefined;
3758
+ const githubClientSecret = typeof source.githubClientSecret === "string" ? source.githubClientSecret.trim() : undefined;
3759
+ const githubPrivateKeyBase64 = typeof source.githubPrivateKeyBase64 === "string" ? source.githubPrivateKeyBase64.trim() : undefined;
3760
+ const githubWebhookSecret = typeof source.githubWebhookSecret === "string" ? source.githubWebhookSecret.trim() : undefined;
3730
3761
  if (!/^[a-f0-9]{64}$/i.test(clusterBootstrapCode))
3731
3762
  throw new Error("cluster bootstrap code must contain exactly 64 hexadecimal characters");
3732
3763
  if (!emailSecret || emailSecret.length > 16384 || /[\r\n\0]/.test(emailSecret))
@@ -3746,12 +3777,29 @@ function validatePlatformBootstrapSecrets(config, input) {
3746
3777
  }
3747
3778
  if (cloudflareTunnelToken)
3748
3779
  validateCloudflareBootstrapSecretPair({ cloudflareTunnelToken, cloudflareApiToken });
3780
+ const githubConfigured = Boolean(config.runtime.environment.githubApp);
3781
+ if (githubConfigured !== Boolean(githubClientSecret && githubPrivateKeyBase64 && githubWebhookSecret)) {
3782
+ throw new Error("GitHub App coordinates require client secret, private key and webhook secret together");
3783
+ }
3784
+ if (githubConfigured) {
3785
+ if (githubClientSecret.length < 20 || githubClientSecret.length > 512 || /[\r\n\0]/.test(githubClientSecret) || githubWebhookSecret.length < 32 || githubWebhookSecret.length > 512 || /[\r\n\0]/.test(githubWebhookSecret)) {
3786
+ throw new Error("GitHub App client or webhook secret is malformed");
3787
+ }
3788
+ try {
3789
+ const pem = Buffer.from(githubPrivateKeyBase64, "base64").toString("utf8");
3790
+ if (createPrivateKey(pem).asymmetricKeyType !== "rsa")
3791
+ throw new Error("not RSA");
3792
+ } catch {
3793
+ throw new Error("GitHub App private key must be a base64-encoded RSA private key");
3794
+ }
3795
+ }
3749
3796
  return {
3750
3797
  clusterBootstrapCode,
3751
3798
  emailSecret,
3752
3799
  ...enrolmentToken ? { enrolmentToken } : {},
3753
3800
  ...backupS3Secret ? { backupS3Secret } : {},
3754
- ...cloudflareTunnelToken ? { cloudflareTunnelToken, cloudflareApiToken } : {}
3801
+ ...cloudflareTunnelToken ? { cloudflareTunnelToken, cloudflareApiToken } : {},
3802
+ ...githubConfigured ? { githubClientSecret, githubPrivateKeyBase64, githubWebhookSecret } : {}
3755
3803
  };
3756
3804
  }
3757
3805
  var platformBootstrapRunner = (config) => config.kind === "platform" && config.database.role === "master";
@@ -4303,6 +4351,17 @@ function approvedPlatformRepairIdentityDigests(config) {
4303
4351
  delete beforeAttestation.runtime.environment.initialInventory.attestation;
4304
4352
  prior.push(beforeAttestation);
4305
4353
  }
4354
+ const metalIdentity = config.runtime.environment.initialInventory?.metalIdentity;
4355
+ if (metalIdentity && config.enrolment.source === "genesis-derived") {
4356
+ const beforeMetalIdentity = structuredClone(config);
4357
+ delete beforeMetalIdentity.runtime.environment.initialInventory.metalIdentity;
4358
+ prior.push(beforeMetalIdentity);
4359
+ }
4360
+ if (config.database.role === "joiner" && config.enrolment.source === "genesis-derived" && config.runtime.environment.custodianEmail) {
4361
+ const beforeReplicaCustodianEmail = structuredClone(config);
4362
+ delete beforeReplicaCustodianEmail.runtime.environment.custodianEmail;
4363
+ prior.push(beforeReplicaCustodianEmail);
4364
+ }
4306
4365
  const telemetryMigration = config.environment === "development" && config.telemetryEndpoint === LOCAL_DEBUG_OTEL_EXPORT && config.runtime.environment.agentOtlpEndpoint === "http://127.0.0.1:4318";
4307
4366
  if (telemetryMigration) {
4308
4367
  const beforeTelemetry = structuredClone(config);
@@ -4655,7 +4714,10 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4655
4714
  enrolmentToken: checked4.enrolmentToken,
4656
4715
  backup: checked4.backupS3Secret,
4657
4716
  cloudflareTunnelToken: checked4.cloudflareTunnelToken,
4658
- cloudflareApiToken: checked4.cloudflareApiToken
4717
+ cloudflareApiToken: checked4.cloudflareApiToken,
4718
+ githubClientSecret: checked4.githubClientSecret,
4719
+ githubPrivateKey: checked4.githubPrivateKeyBase64 ? Buffer.from(checked4.githubPrivateKeyBase64, "base64").toString("utf8") : undefined,
4720
+ githubWebhookSecret: checked4.githubWebhookSecret
4659
4721
  };
4660
4722
  })() : undefined;
4661
4723
  const enrolledPrivate = config.kind === "enrolled-compute" ? validateEnrolledComputeBootstrapSecrets(config, secrets) : undefined;
@@ -4753,6 +4815,9 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4753
4815
  const emailCredentialName = config.runtime.environment.email?.provider === "smtp" ? "fz_smtp.password" : config.runtime.environment.email?.provider === "jetemail" ? "fz_jetemail.apiKey" : undefined;
4754
4816
  for (const [name, source] of Object.entries({
4755
4817
  ...emailCredentialName ? { [emailCredentialName]: platformPrivate.email } : {},
4818
+ "fz_github.clientSecret": platformPrivate.githubClientSecret,
4819
+ "fz_github.privateKey": platformPrivate.githubPrivateKey,
4820
+ "fz_github.webhookSecret": platformPrivate.githubWebhookSecret,
4756
4821
  "backup.s3.secretAccessKey": platformPrivate.backup
4757
4822
  })) {
4758
4823
  if (source) {
@@ -4769,6 +4834,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4769
4834
  const credentials = platformApiCredentialSpecs({
4770
4835
  emailProvider: runtime.environment.email?.provider,
4771
4836
  cloudflareKv: cloudflareConfigured,
4837
+ githubApp: Boolean(runtime.environment.githubApp),
4772
4838
  realtime: Boolean(runtime.environment.realtime)
4773
4839
  });
4774
4840
  const units = renderPlatformApiUnits({
@@ -5496,7 +5562,7 @@ var SUPPORTED_BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dc
5496
5562
 
5497
5563
  class MetalBootstrapError extends Error {
5498
5564
  }
5499
- function normalizeMetalPublicIdentity(value) {
5565
+ function parseMetalPublicIdentity(value) {
5500
5566
  let parsed;
5501
5567
  try {
5502
5568
  parsed = JSON.parse(value);
@@ -5511,7 +5577,13 @@ function normalizeMetalPublicIdentity(value) {
5511
5577
  if (Object.keys(identity).some((key) => !["nodeKey", "publicKeys"].includes(key)) || typeof identity.nodeKey !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(identity.nodeKey) || !keys || Object.keys(keys).some((key) => !["ed25519", "mlDsa"].includes(key)) || keys.ed25519 !== identity.nodeKey || typeof keys.mlDsa !== "string" || !/^[A-Za-z0-9_-]{1,8192}$/.test(keys.mlDsa)) {
5512
5578
  throw new MetalBootstrapError("metal public identity proof was invalid");
5513
5579
  }
5514
- return JSON.stringify(parsed);
5580
+ return {
5581
+ nodeKey: identity.nodeKey,
5582
+ publicKeys: { ed25519: keys.ed25519, mlDsa: keys.mlDsa }
5583
+ };
5584
+ }
5585
+ function normalizeMetalPublicIdentity(value) {
5586
+ return JSON.stringify(parseMetalPublicIdentity(value));
5515
5587
  }
5516
5588
  var defaultExec2 = async (argv, stdin) => {
5517
5589
  const child = Bun.spawn([...argv], {
@@ -6407,7 +6479,7 @@ function writeOperatorPlatformBootstrapFleetRequest(path, requestFiles, cloudfla
6407
6479
  return writeOwnerJson2(path, request, "operator fleet request");
6408
6480
  }
6409
6481
  var validateMetalRequestValue = (value, options = {}) => {
6410
- const root = exactKeys3(value, ["kind", "target", "metalConfigFile", "genesis"], "operator metal request");
6482
+ const root = exactKeys3(value, ["kind", "target", "metalConfigFile", "metalIdentityEvidenceFile", "genesis"], "operator metal request");
6411
6483
  if (root.kind !== "metal-remote")
6412
6484
  throw new Error("operator metal bootstrap kind must be metal-remote");
6413
6485
  const targetValue = exactKeys3(root.target, ["address", "port", "user", "hostKey", "hostKeySha256", "identityPublicKeyFile", "agentSocket"], "operator metal target");
@@ -6426,6 +6498,12 @@ var validateMetalRequestValue = (value, options = {}) => {
6426
6498
  if (typeof root.metalConfigFile !== "string")
6427
6499
  throw new Error("metalConfigFile must be an absolute path");
6428
6500
  canonicalOutputPath(root.metalConfigFile, "metalConfigFile");
6501
+ if (root.metalIdentityEvidenceFile !== undefined && typeof root.metalIdentityEvidenceFile !== "string") {
6502
+ throw new Error("metalIdentityEvidenceFile must be an absolute path");
6503
+ }
6504
+ if (typeof root.metalIdentityEvidenceFile === "string") {
6505
+ canonicalOutputPath(root.metalIdentityEvidenceFile, "metalIdentityEvidenceFile");
6506
+ }
6429
6507
  if (options.validateMetalConfig !== false)
6430
6508
  readMetalBootstrapConfig(root.metalConfigFile);
6431
6509
  const genesis = exactKeys3(root.genesis, ["nodes", "rehearsalNode", "sshPublicKeyFiles"], "operator metal genesis");
@@ -6474,6 +6552,7 @@ var validateMetalRequestValue = (value, options = {}) => {
6474
6552
  return {
6475
6553
  kind: "metal-remote",
6476
6554
  metalConfigFile: root.metalConfigFile,
6555
+ ...typeof root.metalIdentityEvidenceFile === "string" ? { metalIdentityEvidenceFile: root.metalIdentityEvidenceFile } : {},
6477
6556
  target: {
6478
6557
  ...target,
6479
6558
  identityPublicKeyFile: targetValue.identityPublicKeyFile,
@@ -6494,6 +6573,25 @@ function writeOperatorMetalBootstrapRequest(path, request) {
6494
6573
  validateMetalRequestValue(request);
6495
6574
  return writeOwnerJson2(path, request, "operator metal request");
6496
6575
  }
6576
+ function readOperatorMetalIdentityEvidence(path, request) {
6577
+ const value = JSON.parse(new TextDecoder().decode(ownerFile(path, REQUEST_LIMIT, "operator metal identity evidence")));
6578
+ const root = exactKeys3(value, ["format", "kind", "metalHostname", "metalHostKeySha256", "nodeKey", "publicKeys"], "operator metal identity evidence");
6579
+ if (root.format !== 1 || root.kind !== "forgezero-operator-metal-identity" || typeof root.metalHostname !== "string" || typeof root.metalHostKeySha256 !== "string" || typeof root.nodeKey !== "string" || !root.publicKeys || typeof root.publicKeys !== "object") {
6580
+ throw new Error("operator metal identity evidence is malformed");
6581
+ }
6582
+ const identity = parseMetalPublicIdentity(JSON.stringify({ nodeKey: root.nodeKey, publicKeys: root.publicKeys }));
6583
+ const config = readMetalBootstrapConfig(request.metalConfigFile, { allowHistoricalRelease: true });
6584
+ if (root.metalHostname !== config.metalHostname || root.metalHostKeySha256 !== request.target.hostKeySha256) {
6585
+ throw new Error("operator metal identity evidence does not match the reviewed Metal target");
6586
+ }
6587
+ return {
6588
+ format: 1,
6589
+ kind: "forgezero-operator-metal-identity",
6590
+ metalHostname: root.metalHostname,
6591
+ metalHostKeySha256: root.metalHostKeySha256,
6592
+ ...identity
6593
+ };
6594
+ }
6497
6595
  var validateGuestHostKeyEvidenceValue = (value, request) => {
6498
6596
  const root = exactKeys3(value, ["format", "kind", "metalHostKeySha256", "nodes"], "operator guest host-key evidence");
6499
6597
  if (root.format !== 1 || root.kind !== "forgezero-operator-guest-host-keys" || typeof root.metalHostKeySha256 !== "string" || !Array.isArray(root.nodes)) {
@@ -7274,6 +7372,25 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
7274
7372
  `${REMOTE_STAGE}/metal-config.json`,
7275
7373
  "--apply"
7276
7374
  ], "remote typed metal bootstrap");
7375
+ if (request.metalIdentityEvidenceFile) {
7376
+ let applied;
7377
+ try {
7378
+ applied = JSON.parse(output);
7379
+ } catch {
7380
+ throw new Error("remote Metal bootstrap did not return identity evidence");
7381
+ }
7382
+ const publicIdentity2 = applied?.publicIdentity;
7383
+ if (typeof publicIdentity2 !== "string")
7384
+ throw new Error("remote Metal bootstrap omitted its public identity");
7385
+ const identity = parseMetalPublicIdentity(publicIdentity2);
7386
+ writeOwnerJson2(request.metalIdentityEvidenceFile, {
7387
+ format: 1,
7388
+ kind: "forgezero-operator-metal-identity",
7389
+ metalHostname: config.metalHostname,
7390
+ metalHostKeySha256: request.target.hostKeySha256,
7391
+ ...identity
7392
+ }, "operator metal identity evidence");
7393
+ }
7277
7394
  return { plan, output };
7278
7395
  } finally {
7279
7396
  if (knownHosts) {
@@ -7294,6 +7411,7 @@ export {
7294
7411
  redactOperatorSecretDiagnostic,
7295
7412
  readOperatorPlatformBootstrapRequest,
7296
7413
  readOperatorPlatformBootstrapFleetRequest,
7414
+ readOperatorMetalIdentityEvidence,
7297
7415
  readOperatorMetalBootstrapRequest,
7298
7416
  readOperatorGuestHostKeyEvidence,
7299
7417
  planOperatorPlatformFleetBootstrap,
@@ -19,6 +19,14 @@ export interface PlatformInitialInventoryCompute {
19
19
  }
20
20
  export interface PlatformInitialInventory {
21
21
  metalHostname: string;
22
+ /** Hybrid public identity proved by the pinned Metal bootstrap transport. */
23
+ metalIdentity?: {
24
+ nodeKey: string;
25
+ publicKeys: {
26
+ ed25519: string;
27
+ mlDsa: string;
28
+ };
29
+ };
22
30
  region: {
23
31
  key: string;
24
32
  label: string;
@@ -60,6 +68,16 @@ export type PlatformBootstrapEmail = {
60
68
  from: string;
61
69
  eu: boolean;
62
70
  };
71
+ /**
72
+ * Public coordinates for one GitHub App. Its client secret, RSA private key and
73
+ * webhook secret are attended inputs sealed as systemd credentials and later
74
+ * imported into the unlocked platform Vault.
75
+ */
76
+ export interface PlatformBootstrapGitHubApp {
77
+ clientId: string;
78
+ appId: string;
79
+ slug: string;
80
+ }
63
81
  export interface PlatformSharedEnvironment {
64
82
  softwareProfile: PlatformSoftwareProfile;
65
83
  databaseRole: PlatformDatabaseRole;
@@ -91,6 +109,7 @@ export interface PlatformSharedEnvironment {
91
109
  otlpTraceSampleRatio: number;
92
110
  custodianEmail?: string;
93
111
  email?: PlatformBootstrapEmail;
112
+ githubApp?: PlatformBootstrapGitHubApp;
94
113
  backup?: {
95
114
  endpoint: string;
96
115
  region: string;
@@ -118,12 +137,13 @@ export declare function validatePlatformSharedEnvironment(input: PlatformSharedE
118
137
  /** Render only non-secret runtime coordinates. Passwords/tokens have no field in this contract. */
119
138
  export declare function renderPlatformSharedEnvironment(input: PlatformSharedEnvironment): string;
120
139
  export interface SystemdCredentialSpec {
121
- name: 'arangodb-jwt' | 'seed-sync-root' | 'fz_smtp.password' | 'fz_jetemail.apiKey' | 'CF_API_TOKEN' | 'CF_TUNNEL_TOKEN' | 'REALTIME_PUBLISH_SECRET' | 'REALTIME_TICKET_SECRET';
140
+ name: 'arangodb-jwt' | 'seed-sync-root' | 'fz_smtp.password' | 'fz_jetemail.apiKey' | 'fz_github.clientSecret' | 'fz_github.privateKey' | 'fz_github.webhookSecret' | 'CF_API_TOKEN' | 'CF_TUNNEL_TOKEN' | 'REALTIME_PUBLISH_SECRET' | 'REALTIME_TICKET_SECRET';
122
141
  encryptedPath: string;
123
142
  required: boolean;
124
143
  }
125
144
  export declare function platformApiCredentialSpecs(options: {
126
145
  emailProvider?: PlatformBootstrapEmail['provider'];
146
+ githubApp: boolean;
127
147
  cloudflareKv: boolean;
128
148
  realtime: boolean;
129
149
  }): SystemdCredentialSpec[];
@@ -730,10 +730,16 @@ var httpsOrigin = (name, raw) => {
730
730
  };
731
731
  var agentTelemetryOrigin = (raw) => raw === "http://127.0.0.1:4318" ? raw : httpsOrigin("agentOtlpEndpoint", raw);
732
732
  function validatePlatformInitialInventory(value) {
733
- if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["metalHostname", "region", "computes", "attestation", "deployment"].includes(key)) || !/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(value.metalHostname) || !value.region || typeof value.region !== "object" || Array.isArray(value.region) || Object.keys(value.region).some((key) => !["key", "label", "country", "city", "confidentialCapable"].includes(key)) || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.region.key) || !/^[A-Z]{2}$/.test(value.region.country) || value.region.confidentialCapable !== true || !Array.isArray(value.computes)) {
733
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["metalHostname", "metalIdentity", "region", "computes", "attestation", "deployment"].includes(key)) || !/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(value.metalHostname) || !value.region || typeof value.region !== "object" || Array.isArray(value.region) || Object.keys(value.region).some((key) => !["key", "label", "country", "city", "confidentialCapable"].includes(key)) || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.region.key) || !/^[A-Z]{2}$/.test(value.region.country) || value.region.confidentialCapable !== true || !Array.isArray(value.computes)) {
734
734
  throw new Error("initial platform inventory coordinates are invalid");
735
735
  }
736
736
  safeAtom("initialInventory.region.label", value.region.label);
737
+ if (value.metalIdentity !== undefined) {
738
+ const identity = value.metalIdentity;
739
+ if (!identity || typeof identity !== "object" || Array.isArray(identity) || Object.keys(identity).some((key) => !["nodeKey", "publicKeys"].includes(key)) || !/^[A-Za-z0-9_-]{43}$/.test(identity.nodeKey) || !identity.publicKeys || identity.publicKeys.ed25519 !== identity.nodeKey || !/^[A-Za-z0-9_-]{1,8192}$/.test(identity.publicKeys.mlDsa) || Object.keys(identity.publicKeys).some((key) => !["ed25519", "mlDsa"].includes(key))) {
740
+ throw new Error("initial platform Metal identity is invalid");
741
+ }
742
+ }
737
743
  if (value.region.city !== undefined)
738
744
  safeAtom("initialInventory.region.city", value.region.city);
739
745
  if (value.deployment !== undefined && (!value.deployment || typeof value.deployment !== "object" || Array.isArray(value.deployment) || Object.keys(value.deployment).some((key) => !["source", "branch", "revision", "bundleSha256"].includes(key)) || value.deployment.source !== "bootstrap-bundle" || !["dev", "main"].includes(value.deployment.branch) || !/^[a-f0-9]{40}$/.test(value.deployment.revision) || !/^[a-f0-9]{64}$/.test(value.deployment.bundleSha256)))
@@ -768,6 +774,7 @@ function validatePlatformInitialInventory(value) {
768
774
  }
769
775
  return {
770
776
  metalHostname: value.metalHostname,
777
+ ...value.metalIdentity ? { metalIdentity: structuredClone(value.metalIdentity) } : {},
771
778
  region: { ...value.region },
772
779
  computes: value.computes.map((compute, index) => ({
773
780
  ...computes[index],
@@ -821,6 +828,11 @@ function validatePlatformSharedEnvironment(input) {
821
828
  } else if (input.email !== undefined) {
822
829
  throw new Error("Bootstrap email provider must be smtp or jetemail.");
823
830
  }
831
+ if (input.githubApp) {
832
+ if (!/^(?:Iv1\.[A-Fa-f0-9]{16}|Ov23li[A-Za-z0-9]{14,})$/.test(input.githubApp.clientId) || !/^[1-9][0-9]{0,19}$/.test(input.githubApp.appId) || !/^[a-z0-9][a-z0-9-]{0,99}$/.test(input.githubApp.slug)) {
833
+ throw new Error("GitHub App client id, app id or slug is malformed.");
834
+ }
835
+ }
824
836
  boundedInteger("publicApiPort", input.publicApiPort, 1024, 65533);
825
837
  if (!Array.isArray(input.seedSyncMembers) || input.seedSyncMembers.length < 3 || input.seedSyncMembers.length > 64 || new Set(input.seedSyncMembers).size !== input.seedSyncMembers.length) {
826
838
  throw new Error("seedSyncMembers must contain 3 to 64 unique physical host identities.");
@@ -951,6 +963,9 @@ function renderPlatformSharedEnvironment(input) {
951
963
  FZ_REALTIME_WORKER_SCRIPT: value.realtime?.workerScriptName ?? "",
952
964
  FZ_REALTIME_ENDPOINT: value.realtime?.endpoint ?? "",
953
965
  FZ_REALTIME_PRODUCER: value.realtime?.producer ?? "",
966
+ FZ_GITHUB_CLIENT_ID: value.githubApp?.clientId ?? "",
967
+ FZ_GITHUB_APP_ID: value.githubApp?.appId ?? "",
968
+ FZ_GITHUB_APP_SLUG: value.githubApp?.slug ?? "",
954
969
  FZ_PLATFORM_INITIAL_INVENTORY: value.initialInventory ? JSON.stringify(value.initialInventory) : ""
955
970
  };
956
971
  return `# Generated by fz bootstrap platform. Non-secret coordinates only.
@@ -962,6 +977,9 @@ function platformApiCredentialSpecs(options) {
962
977
  const optional = [
963
978
  ["fz_smtp.password", options.emailProvider === "smtp"],
964
979
  ["fz_jetemail.apiKey", options.emailProvider === "jetemail"],
980
+ ["fz_github.clientSecret", options.githubApp],
981
+ ["fz_github.privateKey", options.githubApp],
982
+ ["fz_github.webhookSecret", options.githubApp],
965
983
  ["CF_API_TOKEN", options.cloudflareKv],
966
984
  ["CF_TUNNEL_TOKEN", options.cloudflareKv],
967
985
  ["REALTIME_PUBLISH_SECRET", options.realtime],
@@ -2908,7 +2908,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
2908
2908
  }
2909
2909
 
2910
2910
  // src/version.ts
2911
- var VERSION3 = "0.1.101";
2911
+ var VERSION3 = "0.1.103";
2912
2912
 
2913
2913
  // src/egress-policy.ts
2914
2914
  import { realpathSync as realpathSync3 } from "node:fs";
@@ -4142,10 +4142,16 @@ var httpsOrigin = (name, raw) => {
4142
4142
  };
4143
4143
  var agentTelemetryOrigin = (raw) => raw === "http://127.0.0.1:4318" ? raw : httpsOrigin("agentOtlpEndpoint", raw);
4144
4144
  function validatePlatformInitialInventory(value) {
4145
- if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["metalHostname", "region", "computes", "attestation", "deployment"].includes(key)) || !/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(value.metalHostname) || !value.region || typeof value.region !== "object" || Array.isArray(value.region) || Object.keys(value.region).some((key) => !["key", "label", "country", "city", "confidentialCapable"].includes(key)) || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.region.key) || !/^[A-Z]{2}$/.test(value.region.country) || value.region.confidentialCapable !== true || !Array.isArray(value.computes)) {
4145
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["metalHostname", "metalIdentity", "region", "computes", "attestation", "deployment"].includes(key)) || !/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(value.metalHostname) || !value.region || typeof value.region !== "object" || Array.isArray(value.region) || Object.keys(value.region).some((key) => !["key", "label", "country", "city", "confidentialCapable"].includes(key)) || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.region.key) || !/^[A-Z]{2}$/.test(value.region.country) || value.region.confidentialCapable !== true || !Array.isArray(value.computes)) {
4146
4146
  throw new Error("initial platform inventory coordinates are invalid");
4147
4147
  }
4148
4148
  safeAtom("initialInventory.region.label", value.region.label);
4149
+ if (value.metalIdentity !== undefined) {
4150
+ const identity = value.metalIdentity;
4151
+ if (!identity || typeof identity !== "object" || Array.isArray(identity) || Object.keys(identity).some((key) => !["nodeKey", "publicKeys"].includes(key)) || !/^[A-Za-z0-9_-]{43}$/.test(identity.nodeKey) || !identity.publicKeys || identity.publicKeys.ed25519 !== identity.nodeKey || !/^[A-Za-z0-9_-]{1,8192}$/.test(identity.publicKeys.mlDsa) || Object.keys(identity.publicKeys).some((key) => !["ed25519", "mlDsa"].includes(key))) {
4152
+ throw new Error("initial platform Metal identity is invalid");
4153
+ }
4154
+ }
4149
4155
  if (value.region.city !== undefined)
4150
4156
  safeAtom("initialInventory.region.city", value.region.city);
4151
4157
  if (value.deployment !== undefined && (!value.deployment || typeof value.deployment !== "object" || Array.isArray(value.deployment) || Object.keys(value.deployment).some((key) => !["source", "branch", "revision", "bundleSha256"].includes(key)) || value.deployment.source !== "bootstrap-bundle" || !["dev", "main"].includes(value.deployment.branch) || !/^[a-f0-9]{40}$/.test(value.deployment.revision) || !/^[a-f0-9]{64}$/.test(value.deployment.bundleSha256)))
@@ -4180,6 +4186,7 @@ function validatePlatformInitialInventory(value) {
4180
4186
  }
4181
4187
  return {
4182
4188
  metalHostname: value.metalHostname,
4189
+ ...value.metalIdentity ? { metalIdentity: structuredClone(value.metalIdentity) } : {},
4183
4190
  region: { ...value.region },
4184
4191
  computes: value.computes.map((compute, index) => ({
4185
4192
  ...computes[index],
@@ -4233,6 +4240,11 @@ function validatePlatformSharedEnvironment(input) {
4233
4240
  } else if (input.email !== undefined) {
4234
4241
  throw new Error("Bootstrap email provider must be smtp or jetemail.");
4235
4242
  }
4243
+ if (input.githubApp) {
4244
+ if (!/^(?:Iv1\.[A-Fa-f0-9]{16}|Ov23li[A-Za-z0-9]{14,})$/.test(input.githubApp.clientId) || !/^[1-9][0-9]{0,19}$/.test(input.githubApp.appId) || !/^[a-z0-9][a-z0-9-]{0,99}$/.test(input.githubApp.slug)) {
4245
+ throw new Error("GitHub App client id, app id or slug is malformed.");
4246
+ }
4247
+ }
4236
4248
  boundedInteger("publicApiPort", input.publicApiPort, 1024, 65533);
4237
4249
  if (!Array.isArray(input.seedSyncMembers) || input.seedSyncMembers.length < 3 || input.seedSyncMembers.length > 64 || new Set(input.seedSyncMembers).size !== input.seedSyncMembers.length) {
4238
4250
  throw new Error("seedSyncMembers must contain 3 to 64 unique physical host identities.");
@@ -4363,6 +4375,9 @@ function renderPlatformSharedEnvironment(input) {
4363
4375
  FZ_REALTIME_WORKER_SCRIPT: value.realtime?.workerScriptName ?? "",
4364
4376
  FZ_REALTIME_ENDPOINT: value.realtime?.endpoint ?? "",
4365
4377
  FZ_REALTIME_PRODUCER: value.realtime?.producer ?? "",
4378
+ FZ_GITHUB_CLIENT_ID: value.githubApp?.clientId ?? "",
4379
+ FZ_GITHUB_APP_ID: value.githubApp?.appId ?? "",
4380
+ FZ_GITHUB_APP_SLUG: value.githubApp?.slug ?? "",
4366
4381
  FZ_PLATFORM_INITIAL_INVENTORY: value.initialInventory ? JSON.stringify(value.initialInventory) : ""
4367
4382
  };
4368
4383
  return `# Generated by fz bootstrap platform. Non-secret coordinates only.
@@ -4374,6 +4389,9 @@ function platformApiCredentialSpecs(options) {
4374
4389
  const optional = [
4375
4390
  ["fz_smtp.password", options.emailProvider === "smtp"],
4376
4391
  ["fz_jetemail.apiKey", options.emailProvider === "jetemail"],
4392
+ ["fz_github.clientSecret", options.githubApp],
4393
+ ["fz_github.privateKey", options.githubApp],
4394
+ ["fz_github.webhookSecret", options.githubApp],
4377
4395
  ["CF_API_TOKEN", options.cloudflareKv],
4378
4396
  ["CF_TUNNEL_TOKEN", options.cloudflareKv],
4379
4397
  ["REALTIME_PUBLISH_SECRET", options.realtime],
@@ -4727,7 +4745,7 @@ function planLocalOtlpProof(endpoint, collectorUnit) {
4727
4745
  import { lstatSync as lstatSync5 } from "node:fs";
4728
4746
 
4729
4747
  // src/bootstrap.ts
4730
- import { createHash as createHash7, createHmac as createHmac2, randomBytes as randomBytes3 } from "node:crypto";
4748
+ import { createHash as createHash7, createHmac as createHmac2, createPrivateKey, randomBytes as randomBytes3 } from "node:crypto";
4731
4749
  import {
4732
4750
  chmodSync as chmodSync9,
4733
4751
  existsSync as existsSync11,
@@ -15,4 +15,4 @@ export declare function platformGenesisBootstrapConfigs(template: PlatformBootst
15
15
  branch: 'dev' | 'main';
16
16
  revision: string;
17
17
  bundleSha256: string;
18
- }, attestation?: import('./platform-bootstrap-runtime').PlatformInitialInventory['attestation']): readonly PlatformBootstrapConfig[];
18
+ }, attestation?: import('./platform-bootstrap-runtime').PlatformInitialInventory['attestation'], metalIdentity?: NonNullable<import('./platform-bootstrap-runtime').PlatformInitialInventory['metalIdentity']>): readonly PlatformBootstrapConfig[];
@@ -45,6 +45,11 @@ export interface ForgeZeroLaunchOwnerInput {
45
45
  from: string;
46
46
  eu: boolean;
47
47
  };
48
+ githubApp?: {
49
+ clientId: string;
50
+ appId: string;
51
+ slug: string;
52
+ };
48
53
  }
49
54
  /**
50
55
  * ForgeZero's own genesis public coordinates. They are reviewed source data,
package/dist/provision.js CHANGED
@@ -2908,7 +2908,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
2908
2908
  }
2909
2909
 
2910
2910
  // src/version.ts
2911
- var VERSION3 = "0.1.101";
2911
+ var VERSION3 = "0.1.103";
2912
2912
 
2913
2913
  // src/egress-policy.ts
2914
2914
  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.101";
2
+ export declare const VERSION = "0.1.103";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/agent",
3
- "version": "0.1.101",
3
+ "version": "0.1.103",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",