@treeseed/sdk 0.5.3 → 0.6.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.
Files changed (66) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +46 -0
  3. package/dist/operations/providers/default.js +1 -1
  4. package/dist/operations/services/config-runtime.d.ts +49 -42
  5. package/dist/operations/services/config-runtime.js +449 -136
  6. package/dist/operations/services/deploy.d.ts +298 -0
  7. package/dist/operations/services/deploy.js +381 -137
  8. package/dist/operations/services/git-workflow.d.ts +9 -0
  9. package/dist/operations/services/git-workflow.js +32 -0
  10. package/dist/operations/services/github-api.d.ts +115 -0
  11. package/dist/operations/services/github-api.js +455 -0
  12. package/dist/operations/services/github-automation.d.ts +19 -33
  13. package/dist/operations/services/github-automation.js +44 -131
  14. package/dist/operations/services/key-agent.d.ts +20 -1
  15. package/dist/operations/services/key-agent.js +267 -102
  16. package/dist/operations/services/knowledge-coop-launch.d.ts +2 -3
  17. package/dist/operations/services/knowledge-coop-launch.js +26 -12
  18. package/dist/operations/services/project-platform.d.ts +157 -150
  19. package/dist/operations/services/project-platform.js +129 -26
  20. package/dist/operations/services/railway-api.d.ts +244 -0
  21. package/dist/operations/services/railway-api.js +882 -0
  22. package/dist/operations/services/railway-deploy.d.ts +171 -27
  23. package/dist/operations/services/railway-deploy.js +672 -172
  24. package/dist/operations/services/runtime-tools.d.ts +18 -0
  25. package/dist/operations/services/runtime-tools.js +19 -6
  26. package/dist/operations/services/workspace-preflight.js +2 -2
  27. package/dist/platform/contracts.d.ts +7 -0
  28. package/dist/platform/deploy-config.js +23 -0
  29. package/dist/platform/deploy-runtime.d.ts +1 -0
  30. package/dist/platform/deploy-runtime.js +7 -9
  31. package/dist/platform/env.yaml +10 -9
  32. package/dist/platform/environment.js +4 -0
  33. package/dist/platform/plugin.d.ts +6 -0
  34. package/dist/platform/plugins/constants.d.ts +1 -0
  35. package/dist/platform/plugins/constants.js +1 -0
  36. package/dist/platform/plugins/runtime.d.ts +4 -0
  37. package/dist/platform/plugins/runtime.js +8 -1
  38. package/dist/platform/published-content.js +27 -4
  39. package/dist/platform/tenant/runtime-config.js +33 -24
  40. package/dist/plugin-default.d.ts +1 -0
  41. package/dist/plugin-default.js +1 -0
  42. package/dist/reconcile/builtin-adapters.d.ts +3 -0
  43. package/dist/reconcile/builtin-adapters.js +2093 -0
  44. package/dist/reconcile/contracts.d.ts +155 -0
  45. package/dist/reconcile/contracts.js +0 -0
  46. package/dist/reconcile/desired-state.d.ts +179 -0
  47. package/dist/reconcile/desired-state.js +319 -0
  48. package/dist/reconcile/engine.d.ts +405 -0
  49. package/dist/reconcile/engine.js +356 -0
  50. package/dist/reconcile/errors.d.ts +5 -0
  51. package/dist/reconcile/errors.js +13 -0
  52. package/dist/reconcile/index.d.ts +7 -0
  53. package/dist/reconcile/index.js +7 -0
  54. package/dist/reconcile/registry.d.ts +7 -0
  55. package/dist/reconcile/registry.js +64 -0
  56. package/dist/reconcile/state.d.ts +7 -0
  57. package/dist/reconcile/state.js +303 -0
  58. package/dist/reconcile/units.d.ts +6 -0
  59. package/dist/reconcile/units.js +68 -0
  60. package/dist/scripts/config-treeseed.js +27 -19
  61. package/dist/scripts/tenant-deploy.js +35 -14
  62. package/dist/workflow/operations.js +127 -22
  63. package/dist/workflow-support.d.ts +3 -1
  64. package/dist/workflow-support.js +50 -0
  65. package/dist/workflow.d.ts +2 -0
  66. package/package.json +7 -1
@@ -13,20 +13,36 @@ import {
13
13
  } from "../../platform/environment.js";
14
14
  import { loadTreeseedManifest } from "../../platform/tenant-config.js";
15
15
  import {
16
+ buildProvisioningSummary,
16
17
  createPersistentDeployTarget,
17
18
  ensureGeneratedWranglerConfig,
18
19
  loadDeployState,
19
- markDeploymentInitialized,
20
- provisionCloudflareResources,
21
- syncCloudflareSecrets,
22
- verifyProvisionedCloudflareResources
20
+ syncCloudflareSecrets
23
21
  } from "./deploy.js";
22
+ import { collectTreeseedReconcileStatus, reconcileTreeseedTarget } from "../../reconcile/index.js";
24
23
  import { maybeResolveGitHubRepositorySlug } from "./github-automation.js";
25
- import { validateRailwayDeployPrerequisites } from "./railway-deploy.js";
24
+ import {
25
+ buildRailwayCommandEnv
26
+ } from "./railway-deploy.js";
27
+ import {
28
+ normalizeRailwayEnvironmentName,
29
+ resolveRailwayWorkspace
30
+ } from "./railway-api.js";
31
+ import {
32
+ createGitHubApiClient,
33
+ ensureGitHubBranchFromBase,
34
+ listGitHubRepositorySecretNames,
35
+ listGitHubRepositoryVariableNames,
36
+ upsertGitHubRepositorySecret,
37
+ upsertGitHubRepositoryVariable,
38
+ upsertGitHubRepositoryVariableWithGhCli
39
+ } from "./github-api.js";
26
40
  import { loadCliDeployConfig, packageScriptPath, resolveWranglerBin, withProcessCwd } from "./runtime-tools.js";
41
+ import { PRODUCTION_BRANCH, STAGING_BRANCH } from "./git-workflow.js";
27
42
  import {
28
43
  assertTreeseedKeyAgentResponse,
29
44
  getTreeseedKeyAgentPaths,
45
+ inspectTreeseedKeyAgentDiagnostics,
30
46
  readWrappedMachineKeyFile,
31
47
  replaceWrappedMachineKey,
32
48
  rotateWrappedMachineKeyPassphrase,
@@ -55,6 +71,30 @@ const CLI_CHECK_TIMEOUT_MS = 5e3;
55
71
  const DEPRECATED_LOCAL_ENV_FILES = [".env.local", ".dev.vars"];
56
72
  const warnedDeprecatedLocalEnvRoots = /* @__PURE__ */ new Set();
57
73
  const inlineTreeseedSecretSessions = /* @__PURE__ */ new Map();
74
+ const railwayConnectionCheckCache = /* @__PURE__ */ new Map();
75
+ function filterEnvironmentValuesByRegistry(values, registry) {
76
+ const registeredKeys = new Set(registry.entries.map((entry) => entry.id));
77
+ return Object.fromEntries(
78
+ Object.entries(values).filter(([key]) => registeredKeys.has(key))
79
+ );
80
+ }
81
+ function inspectTreeseedPassphraseEnvDiagnostic(env = process.env) {
82
+ const configured = typeof env[TREESEED_MACHINE_KEY_PASSPHRASE_ENV] === "string" && env[TREESEED_MACHINE_KEY_PASSPHRASE_ENV].trim().length > 0;
83
+ return {
84
+ envVar: TREESEED_MACHINE_KEY_PASSPHRASE_ENV,
85
+ configured,
86
+ recommendedLaunch: `Export ${TREESEED_MACHINE_KEY_PASSPHRASE_ENV} in a shell and launch \`code .\` from that shell before starting the Codex session.`
87
+ };
88
+ }
89
+ async function inspectTreeseedKeyAgentTransportDiagnostic() {
90
+ const { socketPath, pidPath } = getTreeseedKeyAgentPaths();
91
+ const diagnostics = await inspectTreeseedKeyAgentDiagnostics(socketPath);
92
+ return {
93
+ socketPath,
94
+ pidPath,
95
+ ...diagnostics
96
+ };
97
+ }
58
98
  function createDefaultRemoteHost() {
59
99
  return {
60
100
  id: "official",
@@ -419,21 +459,26 @@ function unlockTreeseedSecretSessionFromEnv(tenantRoot, options = {}) {
419
459
  startTreeseedKeyAgentDaemon(tenantRoot);
420
460
  let response = { ok: false, code: "daemon_unavailable", message: "Treeseed key agent is unavailable." };
421
461
  for (let attempt = 0; attempt < 20; attempt += 1) {
422
- response = runTreeseedKeyAgentCommand([
423
- "unlock-from-env",
424
- "--key-path",
425
- keyPath,
426
- "--socket-path",
427
- socketPath,
428
- "--idle-timeout-ms",
429
- String(TREESEED_KEY_AGENT_IDLE_TIMEOUT_MS),
430
- ...options.allowMigration === false ? [] : ["--allow-migration"],
431
- ...options.createIfMissing === false ? [] : ["--create-if-missing"]
432
- ]);
433
- if (response.code !== "daemon_unavailable") {
434
- break;
462
+ try {
463
+ const parsed = runTreeseedKeyAgentCommand([
464
+ "unlock-from-env",
465
+ "--key-path",
466
+ keyPath,
467
+ "--socket-path",
468
+ socketPath,
469
+ "--idle-timeout-ms",
470
+ String(TREESEED_KEY_AGENT_IDLE_TIMEOUT_MS),
471
+ ...options.allowMigration === false ? [] : ["--allow-migration"],
472
+ ...options.createIfMissing === false ? [] : ["--create-if-missing"]
473
+ ]);
474
+ assertTreeseedKeyAgentResponse(parsed, `Unable to unlock the Treeseed secret session from ${TREESEED_MACHINE_KEY_PASSPHRASE_ENV}.`);
475
+ return parsed.status;
476
+ } catch (error) {
477
+ if (attempt === 19) {
478
+ throw error;
479
+ }
480
+ sleepMs(25);
435
481
  }
436
- sleepMs(25);
437
482
  }
438
483
  assertTreeseedKeyAgentResponse(
439
484
  response,
@@ -1298,11 +1343,7 @@ function resolveTreeseedMachineEnvironmentValues(tenantRoot, scope) {
1298
1343
  };
1299
1344
  const entryById = new Map(registry.entries.map((entry) => [entry.id, entry]));
1300
1345
  const values = {};
1301
- const knownKeys = /* @__PURE__ */ new Set([
1302
- ...Object.keys(bucketValuesByScope.shared ?? {}),
1303
- ...Object.keys(bucketValuesByScope[scope] ?? {}),
1304
- ...registry.entries.map((entry) => entry.id)
1305
- ]);
1346
+ const knownKeys = new Set(registry.entries.map((entry) => entry.id));
1306
1347
  for (const entryId of knownKeys) {
1307
1348
  const resolved = resolveEntryValueFromBuckets(entryById.get(entryId), entryId, scope, bucketValuesByScope);
1308
1349
  if (typeof resolved === "string" && resolved.length > 0) {
@@ -1349,6 +1390,7 @@ function collectTreeseedEnvironmentContext(tenantRoot) {
1349
1390
  }
1350
1391
  function collectTreeseedConfigSeedValues(tenantRoot, scope, env = process.env) {
1351
1392
  warnDeprecatedTreeseedLocalEnvFiles(tenantRoot);
1393
+ const registry = collectTreeseedEnvironmentContext(tenantRoot);
1352
1394
  let machineValues = {};
1353
1395
  try {
1354
1396
  machineValues = resolveTreeseedMachineEnvironmentValues(tenantRoot, scope);
@@ -1363,17 +1405,22 @@ function collectTreeseedConfigSeedValues(tenantRoot, scope, env = process.env) {
1363
1405
  normalizedEnv[canonicalKey] = normalizedEnv[legacyKey];
1364
1406
  }
1365
1407
  }
1366
- return {
1408
+ return filterEnvironmentValuesByRegistry({
1367
1409
  ...machineValues,
1368
1410
  ...Object.fromEntries(Object.entries(normalizedEnv).map(([key, value]) => [key, value ?? void 0]))
1369
- };
1411
+ }, registry);
1370
1412
  }
1371
1413
  function collectTreeseedConfigSeedValueSources(tenantRoot, scope, env = process.env) {
1372
1414
  warnDeprecatedTreeseedLocalEnvFiles(tenantRoot);
1415
+ const registry = collectTreeseedEnvironmentContext(tenantRoot);
1416
+ const registeredKeys = new Set(registry.entries.map((entry) => entry.id));
1373
1417
  const values = {};
1374
1418
  const sources = {};
1375
1419
  const merge = (source, entries) => {
1376
1420
  for (const [key, value] of Object.entries(entries)) {
1421
+ if (!registeredKeys.has(key)) {
1422
+ continue;
1423
+ }
1377
1424
  if (typeof value !== "string" || value.length === 0) {
1378
1425
  continue;
1379
1426
  }
@@ -1470,7 +1517,7 @@ function assertTreeseedCommandEnvironment({ tenantRoot, scope, purpose }) {
1470
1517
  error.details = report.validation;
1471
1518
  throw error;
1472
1519
  }
1473
- function runGh(args, { cwd, dryRun = false, input } = {}) {
1520
+ function runGh(args, { cwd, dryRun = false, input, env } = {}) {
1474
1521
  if (dryRun) {
1475
1522
  return { status: 0, stdout: "", stderr: "" };
1476
1523
  }
@@ -1478,8 +1525,18 @@ function runGh(args, { cwd, dryRun = false, input } = {}) {
1478
1525
  cwd,
1479
1526
  stdio: input !== void 0 ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"],
1480
1527
  encoding: "utf8",
1481
- input
1528
+ input,
1529
+ timeout: 15e3,
1530
+ env: {
1531
+ ...process.env,
1532
+ ...env ?? {},
1533
+ GH_PROMPT_DISABLED: "1",
1534
+ GH_NO_UPDATE_NOTIFIER: "1"
1535
+ }
1482
1536
  });
1537
+ if (result.error?.code === "ETIMEDOUT") {
1538
+ throw new Error(`gh ${args.join(" ")} timed out`);
1539
+ }
1483
1540
  if (result.status !== 0) {
1484
1541
  throw new Error(result.stderr?.trim() || result.stdout?.trim() || `gh ${args.join(" ")} failed`);
1485
1542
  }
@@ -1637,6 +1694,9 @@ function providerConnectionResult(provider, ready, detail, extra = {}) {
1637
1694
  ...extra
1638
1695
  };
1639
1696
  }
1697
+ function isTransientProviderConnectionError(detail) {
1698
+ return /fetch failed|failed to fetch|timed out|etimedout|econnreset|enetunreach|temporarily unavailable|aborted|api check failed|rate.?limit|too many requests|429/iu.test(detail || "");
1699
+ }
1640
1700
  function checkGitHubConnection({ tenantRoot, env }) {
1641
1701
  if (!env.GH_TOKEN) {
1642
1702
  return providerConnectionResult("github", false, "GH_TOKEN is not configured.", { skipped: true });
@@ -1646,76 +1706,127 @@ function checkGitHubConnection({ tenantRoot, env }) {
1646
1706
  }
1647
1707
  const repository = maybeResolveGitHubRepositorySlug(tenantRoot);
1648
1708
  const args = repository ? ["repo", "view", repository, "--json", "nameWithOwner", "--jq", ".nameWithOwner"] : ["api", "user", "--jq", ".login"];
1649
- const result = spawnSync("gh", args, {
1650
- cwd: tenantRoot,
1651
- stdio: "pipe",
1652
- encoding: "utf8",
1653
- env: { ...process.env, ...env },
1654
- timeout: CLI_CHECK_TIMEOUT_MS
1655
- });
1656
- if (result.status !== 0) {
1657
- return providerConnectionResult("github", false, formatCheckOutput(result) || "GitHub API check failed.");
1658
- }
1659
- const resolved = result.stdout.trim();
1660
- return providerConnectionResult(
1661
- "github",
1662
- true,
1663
- repository ? `GitHub token can access ${resolved || repository}.` : resolved ? `Authenticated as ${resolved}.` : "GitHub API check succeeded."
1664
- );
1665
- }
1666
- function checkCloudflareConnection({ tenantRoot, env }) {
1667
- if (!env.CLOUDFLARE_API_TOKEN) {
1668
- return providerConnectionResult("cloudflare", false, "CLOUDFLARE_API_TOKEN is not configured.", { skipped: true });
1669
- }
1670
- try {
1671
- const result = spawnSync(process.execPath, [resolveWranglerBin(), "whoami"], {
1709
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1710
+ const result = spawnSync("gh", args, {
1672
1711
  cwd: tenantRoot,
1673
1712
  stdio: "pipe",
1674
1713
  encoding: "utf8",
1675
1714
  env: { ...process.env, ...env },
1676
1715
  timeout: CLI_CHECK_TIMEOUT_MS
1677
1716
  });
1678
- if (result.status !== 0) {
1679
- return providerConnectionResult("cloudflare", false, formatCheckOutput(result) || "Cloudflare Wrangler check failed.");
1717
+ if (result.status === 0) {
1718
+ const resolved = result.stdout.trim();
1719
+ return providerConnectionResult(
1720
+ "github",
1721
+ true,
1722
+ repository ? `GitHub token can access ${resolved || repository}.` : resolved ? `Authenticated as ${resolved}.` : "GitHub API check succeeded."
1723
+ );
1724
+ }
1725
+ const detail = formatCheckOutput(result) || "GitHub API check failed.";
1726
+ if (attempt >= 2 || !isTransientProviderConnectionError(detail)) {
1727
+ return providerConnectionResult("github", false, detail);
1680
1728
  }
1681
- return providerConnectionResult("cloudflare", true, "Wrangler authenticated with CLOUDFLARE_API_TOKEN.");
1682
- } catch (error) {
1683
- return providerConnectionResult("cloudflare", false, error instanceof Error ? error.message : "Cloudflare Wrangler check failed.");
1684
1729
  }
1730
+ return providerConnectionResult("github", false, "GitHub API check failed.");
1685
1731
  }
1686
- function checkRailwayConnection({ tenantRoot, env }) {
1687
- if (!env.RAILWAY_API_TOKEN && !env.RAILWAY_TOKEN) {
1688
- return providerConnectionResult("railway", false, "RAILWAY_API_TOKEN or RAILWAY_TOKEN is not configured.", { skipped: true });
1732
+ function checkCloudflareConnection({ tenantRoot, env }) {
1733
+ if (!env.CLOUDFLARE_API_TOKEN) {
1734
+ return providerConnectionResult("cloudflare", false, "CLOUDFLARE_API_TOKEN is not configured.", { skipped: true });
1689
1735
  }
1690
- if (!commandAvailable("railway")) {
1691
- return providerConnectionResult("railway", false, "Railway CLI `railway` is not installed.");
1736
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1737
+ try {
1738
+ const result = spawnSync(process.execPath, [resolveWranglerBin(), "whoami"], {
1739
+ cwd: tenantRoot,
1740
+ stdio: "pipe",
1741
+ encoding: "utf8",
1742
+ env: { ...process.env, ...env },
1743
+ timeout: CLI_CHECK_TIMEOUT_MS
1744
+ });
1745
+ if (result.status === 0) {
1746
+ return providerConnectionResult("cloudflare", true, "Wrangler authenticated with CLOUDFLARE_API_TOKEN.");
1747
+ }
1748
+ const detail = formatCheckOutput(result) || "Cloudflare Wrangler check failed.";
1749
+ if (attempt >= 2 || !isTransientProviderConnectionError(detail)) {
1750
+ return providerConnectionResult("cloudflare", false, detail);
1751
+ }
1752
+ } catch (error) {
1753
+ const detail = error instanceof Error ? error.message : "Cloudflare Wrangler check failed.";
1754
+ if (attempt >= 2 || !isTransientProviderConnectionError(detail)) {
1755
+ return providerConnectionResult("cloudflare", false, detail);
1756
+ }
1757
+ }
1692
1758
  }
1693
- const result = spawnSync("railway", ["whoami"], {
1694
- cwd: tenantRoot,
1695
- stdio: "pipe",
1696
- encoding: "utf8",
1697
- env: { ...process.env, ...env },
1698
- timeout: CLI_CHECK_TIMEOUT_MS
1759
+ return providerConnectionResult(
1760
+ "cloudflare",
1761
+ false,
1762
+ "Cloudflare connectivity preflight hit transient fetch failures; bootstrap will continue and rely on live reconcile verification.",
1763
+ { skipped: true, warning: true, transient: true }
1764
+ );
1765
+ }
1766
+ async function checkRailwayConnection({ tenantRoot, env }) {
1767
+ if (!env.RAILWAY_API_TOKEN) {
1768
+ return providerConnectionResult("railway", false, "RAILWAY_API_TOKEN is not configured.", { skipped: true });
1769
+ }
1770
+ const workspaceName = env.TREESEED_RAILWAY_WORKSPACE || resolveRailwayWorkspace(env);
1771
+ const cacheKey = JSON.stringify({
1772
+ tenantRoot,
1773
+ token: env.RAILWAY_API_TOKEN,
1774
+ workspaceName
1699
1775
  });
1700
- if (result.status !== 0) {
1701
- return providerConnectionResult("railway", false, formatCheckOutput(result) || "Railway CLI check failed.");
1776
+ const cached = railwayConnectionCheckCache.get(cacheKey);
1777
+ if (cached) {
1778
+ return await cached;
1779
+ }
1780
+ const checkPromise = (async () => {
1781
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1782
+ try {
1783
+ const whoami = checkCommand("railway", ["whoami"], { cwd: tenantRoot, env });
1784
+ if (!whoami.ok) {
1785
+ if (/rate.?limit|too many requests|429/iu.test(whoami.detail || "")) {
1786
+ return providerConnectionResult(
1787
+ "railway",
1788
+ false,
1789
+ "Railway connectivity preflight was rate-limited; bootstrap will continue and rely on API-backed reconcile verification.",
1790
+ { skipped: true, warning: true, rateLimited: true }
1791
+ );
1792
+ }
1793
+ throw new Error(whoami.detail || "Railway CLI authentication check failed.");
1794
+ }
1795
+ const identity = whoami.stdout.replace(/^logged in as\s+/iu, "").replace(/\s*👋\s*$/u, "").trim() || "an account";
1796
+ return providerConnectionResult("railway", true, `Railway authenticated as ${identity} in workspace ${workspaceName}. Project and service existence will be reconciled during bootstrap.`);
1797
+ } catch (error) {
1798
+ const detail = error instanceof Error ? error.message : "Railway API check failed.";
1799
+ if (attempt >= 2 || !isTransientProviderConnectionError(detail)) {
1800
+ return providerConnectionResult("railway", false, detail);
1801
+ }
1802
+ }
1803
+ }
1804
+ return providerConnectionResult("railway", false, "Railway API check failed.");
1805
+ })();
1806
+ railwayConnectionCheckCache.set(cacheKey, checkPromise);
1807
+ try {
1808
+ return await checkPromise;
1809
+ } catch (error) {
1810
+ railwayConnectionCheckCache.delete(cacheKey);
1811
+ throw error;
1702
1812
  }
1703
- return providerConnectionResult("railway", true, result.stdout.trim() || "Railway CLI check succeeded.");
1704
1813
  }
1705
- function checkTreeseedProviderConnections({ tenantRoot, scope = "prod", env = process.env } = {}) {
1814
+ async function checkTreeseedProviderConnections({ tenantRoot, scope = "prod", env = process.env } = {}) {
1706
1815
  const values = collectTreeseedConfigSeedValues(tenantRoot, scope, env);
1707
- const commandEnv = {
1816
+ const rawCommandEnv = {
1708
1817
  GH_TOKEN: values.GH_TOKEN,
1709
1818
  CLOUDFLARE_API_TOKEN: values.CLOUDFLARE_API_TOKEN,
1710
1819
  CLOUDFLARE_ACCOUNT_ID: values.CLOUDFLARE_ACCOUNT_ID,
1711
1820
  RAILWAY_API_TOKEN: values.RAILWAY_API_TOKEN,
1712
- RAILWAY_TOKEN: values.RAILWAY_TOKEN
1821
+ TREESEED_RAILWAY_WORKSPACE: values.TREESEED_RAILWAY_WORKSPACE || resolveRailwayWorkspace(values)
1713
1822
  };
1823
+ const commandEnv = buildRailwayCommandEnv(rawCommandEnv);
1714
1824
  const checks = [
1715
1825
  checkGitHubConnection({ tenantRoot, env: commandEnv }),
1716
- checkCloudflareConnection({ tenantRoot, env: commandEnv }),
1717
- checkRailwayConnection({ tenantRoot, env: commandEnv })
1826
+ checkCloudflareConnection({ tenantRoot, env: commandEnv })
1718
1827
  ];
1828
+ const railwayCheck = await checkRailwayConnection({ tenantRoot, env: commandEnv });
1829
+ checks.push(railwayCheck);
1719
1830
  return {
1720
1831
  scope,
1721
1832
  ok: checks.every((check) => check.ready || check.skipped),
@@ -1732,14 +1843,20 @@ function formatTreeseedProviderConnectionReport(report) {
1732
1843
  }
1733
1844
  return lines.join("\n");
1734
1845
  }
1846
+ function formatTreeseedProviderConnectionFailures(reports) {
1847
+ const failing = reports.filter((report) => report.checks.some((check) => !check.ready && !check.skipped));
1848
+ if (failing.length === 0) {
1849
+ return "";
1850
+ }
1851
+ return [
1852
+ "Treeseed provider connection checks failed.",
1853
+ ...failing.map((report) => formatTreeseedProviderConnectionReport(report))
1854
+ ].join("\n");
1855
+ }
1735
1856
  function writeProviderConnectionReport(write, report) {
1736
1857
  write(formatTreeseedProviderConnectionReport(report));
1737
1858
  }
1738
- function listGitHubNames(command, repository, tenantRoot) {
1739
- const result = runGh([command, "list", "--repo", repository, "--json", "name"], { cwd: tenantRoot });
1740
- return new Set(JSON.parse(result.stdout || "[]").map((entry) => entry?.name).filter(Boolean));
1741
- }
1742
- function syncTreeseedGitHubEnvironment({ tenantRoot, scope = "prod", dryRun = false } = {}) {
1859
+ async function syncTreeseedGitHubEnvironment({ tenantRoot, scope = "prod", dryRun = false } = {}) {
1743
1860
  const repository = maybeResolveGitHubRepositorySlug(tenantRoot);
1744
1861
  if (!repository) {
1745
1862
  throw new Error("Unable to determine the GitHub repository from the origin remote.");
@@ -1747,8 +1864,14 @@ function syncTreeseedGitHubEnvironment({ tenantRoot, scope = "prod", dryRun = fa
1747
1864
  const registry = collectTreeseedEnvironmentContext(tenantRoot);
1748
1865
  const values = resolveTreeseedMachineEnvironmentValues(tenantRoot, scope);
1749
1866
  const relevant = registry.entries.filter((entry) => entry.scopes.includes(scope));
1750
- const secretNames = listGitHubNames("secret", repository, tenantRoot);
1751
- const variableNames = listGitHubNames("variable", repository, tenantRoot);
1867
+ const ghToken = values.GH_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN || "";
1868
+ const ghEnv = ghToken ? {
1869
+ GH_TOKEN: ghToken,
1870
+ GITHUB_TOKEN: ghToken
1871
+ } : {};
1872
+ const githubClient = createGitHubApiClient({ env: ghEnv });
1873
+ const secretNames = await listGitHubRepositorySecretNames(repository, { client: githubClient });
1874
+ const variableNames = await listGitHubRepositoryVariableNames(repository, { client: githubClient });
1752
1875
  const synced = {
1753
1876
  secrets: [],
1754
1877
  variables: []
@@ -1759,11 +1882,23 @@ function syncTreeseedGitHubEnvironment({ tenantRoot, scope = "prod", dryRun = fa
1759
1882
  continue;
1760
1883
  }
1761
1884
  if (entry.targets.includes("github-secret")) {
1762
- runGh(["secret", "set", entry.id, "--repo", repository, "--body", value], { cwd: tenantRoot, dryRun });
1885
+ if (!dryRun) {
1886
+ await upsertGitHubRepositorySecret(repository, entry.id, value, { client: githubClient });
1887
+ }
1763
1888
  synced.secrets.push({ name: entry.id, existed: secretNames.has(entry.id) });
1764
1889
  }
1765
1890
  if (entry.targets.includes("github-variable")) {
1766
- runGh(["variable", "set", entry.id, "--repo", repository, "--body", value], { cwd: tenantRoot, dryRun });
1891
+ if (!dryRun) {
1892
+ try {
1893
+ upsertGitHubRepositoryVariableWithGhCli(repository, entry.id, value, { env: ghEnv });
1894
+ } catch (error) {
1895
+ const message = error instanceof Error ? error.message : String(error ?? "");
1896
+ if (!/not found|ENOENT|timed out|timeout|aborted|gh api exited/iu.test(message)) {
1897
+ throw error;
1898
+ }
1899
+ await upsertGitHubRepositoryVariable(repository, entry.id, value, { client: githubClient });
1900
+ }
1901
+ }
1767
1902
  synced.variables.push({ name: entry.id, existed: variableNames.has(entry.id) });
1768
1903
  }
1769
1904
  }
@@ -1815,7 +1950,7 @@ function syncTreeseedRailwayEnvironment({ tenantRoot, scope = "prod", dryRun = f
1815
1950
  serviceId: service.railway?.serviceId ?? "",
1816
1951
  rootDir: resolve(tenantRoot, service.railway?.rootDir ?? service.rootDir ?? defaultRootDir),
1817
1952
  baseUrl: environment?.baseUrl ?? service.publicBaseUrl ?? "(unset)",
1818
- environmentName: environment?.railwayEnvironment ?? scope,
1953
+ environmentName: normalizeRailwayEnvironmentName(environment?.railwayEnvironment ?? scope),
1819
1954
  secrets: railwaySecretNames,
1820
1955
  variables: railwayVariableNames,
1821
1956
  dryRun
@@ -1840,23 +1975,22 @@ function syncTreeseedRailwayEnvironment({ tenantRoot, scope = "prod", dryRun = f
1840
1975
  services
1841
1976
  };
1842
1977
  }
1843
- function initializeTreeseedPersistentEnvironment({ tenantRoot, scope = "prod", dryRun = false } = {}) {
1978
+ async function initializeTreeseedPersistentEnvironment({ tenantRoot, scope = "prod", dryRun = false } = {}) {
1844
1979
  const normalizedScope = scope === "prod" ? "prod" : scope;
1845
1980
  const target = createPersistentDeployTarget(normalizedScope);
1846
- const summary = provisionCloudflareResources(tenantRoot, { dryRun, target });
1847
- ensureGeneratedWranglerConfig(tenantRoot, { target });
1848
- const syncedSecrets = syncCloudflareSecrets(tenantRoot, { dryRun, target });
1849
- if (!dryRun) {
1850
- markDeploymentInitialized(tenantRoot, { target });
1851
- }
1981
+ const summary = await reconcileTreeseedTarget({
1982
+ tenantRoot,
1983
+ target,
1984
+ env: process.env
1985
+ });
1852
1986
  return {
1853
1987
  scope: normalizedScope,
1854
1988
  target,
1855
1989
  summary,
1856
- secrets: syncedSecrets
1990
+ secrets: summary.results.filter((result) => result.unit.provider === "cloudflare").flatMap((result) => Object.keys(result.resourceLocators ?? {}))
1857
1991
  };
1858
1992
  }
1859
- function summarizePersistentReadiness(tenantRoot, scope, validation, connectionChecks) {
1993
+ async function summarizePersistentReadiness(tenantRoot, scope, validation, connectionChecks, env = process.env, { includeReconcileStatus = true } = {}) {
1860
1994
  const validationProblems = [...validation.missing, ...validation.invalid];
1861
1995
  const validationBlockers = validationProblems.map((problem) => problem.message);
1862
1996
  const connectionReady = connectionChecks.every((check) => check.ready || check.skipped);
@@ -1898,37 +2032,105 @@ function summarizePersistentReadiness(tenantRoot, scope, validation, connectionC
1898
2032
  }
1899
2033
  };
1900
2034
  }
1901
- const cloudflare = verifyProvisionedCloudflareResources(tenantRoot, { scope });
1902
- let railwayReady = true;
1903
- let railwayIssue = null;
1904
- try {
1905
- validateRailwayDeployPrerequisites(tenantRoot, scope);
1906
- } catch (error) {
1907
- railwayReady = false;
1908
- railwayIssue = error instanceof Error ? error.message : String(error);
1909
- }
1910
2035
  const configured = validation.ok;
1911
- const provisioned = cloudflare.ok && railwayReady;
1912
- const deployable = configured && provisioned && connectionReady;
1913
- const blockers = [
1914
- ...connectionIssues,
1915
- ...railwayIssue ? [railwayIssue] : []
1916
- ];
1917
- if (!cloudflare.ok) {
1918
- blockers.push("Cloudflare foundational resources have not been fully provisioned yet.");
2036
+ if (!includeReconcileStatus) {
2037
+ return {
2038
+ configured,
2039
+ provisioned: false,
2040
+ deployable: false,
2041
+ phase: "config_complete",
2042
+ blockers: [...connectionIssues],
2043
+ warnings: connectionWarnings,
2044
+ checks: {
2045
+ validation: validation.ok,
2046
+ connections: connectionReady,
2047
+ reconcile: "deferred"
2048
+ }
2049
+ };
1919
2050
  }
2051
+ const reconcile = await collectTreeseedReconcileStatus({
2052
+ tenantRoot,
2053
+ target: createPersistentDeployTarget(scope),
2054
+ env
2055
+ });
2056
+ const provisioned = reconcile.ready;
2057
+ const deployable = configured && provisioned && connectionReady;
2058
+ const blockers = [...connectionIssues, ...reconcile.blockers];
1920
2059
  return {
1921
2060
  configured,
1922
2061
  provisioned,
1923
2062
  deployable,
1924
2063
  phase: provisioned ? "provisioned" : "config_complete",
1925
2064
  blockers,
1926
- warnings: connectionWarnings,
2065
+ warnings: [...connectionWarnings, ...reconcile.warnings],
1927
2066
  checks: {
1928
2067
  validation: validation.ok,
1929
2068
  connections: connectionReady,
1930
- cloudflare: cloudflare.checks,
1931
- railway: railwayReady
2069
+ reconcile: reconcile.units
2070
+ }
2071
+ };
2072
+ }
2073
+ function summarizeReconciledPersistentReadiness(scope, validation, connectionChecks, reconciled) {
2074
+ const validationProblems = [...validation.missing, ...validation.invalid];
2075
+ const validationBlockers = validationProblems.map((problem) => problem.message);
2076
+ const connectionReady = connectionChecks.every((check) => check.ready || check.skipped);
2077
+ const connectionIssues = connectionChecks.filter((check) => !check.ready && !check.skipped).map((check) => `${check.provider}: ${check.detail}`);
2078
+ const connectionWarnings = connectionChecks.filter((check) => check.skipped).map((check) => `${check.provider}: ${check.detail}`);
2079
+ if (scope === "local") {
2080
+ return {
2081
+ configured: validation.ok,
2082
+ provisioned: true,
2083
+ deployable: validation.ok && connectionReady,
2084
+ phase: validation.ok ? "code_ready" : "config_incomplete",
2085
+ blockers: [
2086
+ ...validationBlockers,
2087
+ ...connectionIssues
2088
+ ],
2089
+ warnings: connectionWarnings,
2090
+ checks: {
2091
+ validation: validation.ok,
2092
+ connections: connectionReady
2093
+ }
2094
+ };
2095
+ }
2096
+ if (!validation.ok) {
2097
+ return {
2098
+ configured: false,
2099
+ provisioned: false,
2100
+ deployable: false,
2101
+ phase: "config_incomplete",
2102
+ blockers: [
2103
+ ...validationBlockers,
2104
+ ...connectionIssues
2105
+ ],
2106
+ warnings: connectionWarnings,
2107
+ checks: {
2108
+ validation: false,
2109
+ connections: connectionReady,
2110
+ reconcile: []
2111
+ }
2112
+ };
2113
+ }
2114
+ const actions = reconciled?.actions ?? [];
2115
+ const blockers = actions.filter((action) => action.verified !== true).flatMap((action) => [
2116
+ ...action.missing.map((entry) => `${action.provider}:${action.unitType}: ${entry}`),
2117
+ ...action.drifted.map((entry) => `${action.provider}:${action.unitType}: ${entry}`)
2118
+ ]);
2119
+ const provisioned = blockers.length === 0 && actions.length > 0;
2120
+ return {
2121
+ configured: true,
2122
+ provisioned,
2123
+ deployable: provisioned && connectionReady,
2124
+ phase: provisioned ? "provisioned" : "config_complete",
2125
+ blockers: [
2126
+ ...connectionIssues,
2127
+ ...blockers
2128
+ ],
2129
+ warnings: connectionWarnings,
2130
+ checks: {
2131
+ validation: true,
2132
+ connections: connectionReady,
2133
+ reconcile: actions
1932
2134
  }
1933
2135
  };
1934
2136
  }
@@ -2161,7 +2363,7 @@ function applyTreeseedConfigValues({
2161
2363
  sharedStorageMigrations
2162
2364
  };
2163
2365
  }
2164
- function finalizeTreeseedConfig({
2366
+ async function finalizeTreeseedConfig({
2165
2367
  tenantRoot,
2166
2368
  scopes = [...TREESEED_ENVIRONMENT_SCOPES],
2167
2369
  sync = "all",
@@ -2174,7 +2376,9 @@ function finalizeTreeseedConfig({
2174
2376
  const summary = {
2175
2377
  scopes,
2176
2378
  synced: {},
2177
- initialized: [],
2379
+ reconciled: [],
2380
+ deployed: [],
2381
+ resourceInventoryByScope: {},
2178
2382
  connectionChecks: [],
2179
2383
  validationByScope: {},
2180
2384
  readinessByScope: {}
@@ -2185,9 +2389,24 @@ function finalizeTreeseedConfig({
2185
2389
  }
2186
2390
  };
2187
2391
  progress(`Validating configuration for ${scopes.join(", ")}...`);
2392
+ const scopeSeedValues = Object.fromEntries(
2393
+ scopes.map((scope) => [scope, collectTreeseedConfigSeedValues(tenantRoot, scope, env)])
2394
+ );
2188
2395
  for (const scope of scopes) {
2396
+ const seedValues = scopeSeedValues[scope];
2397
+ const suggestedValues = getTreeseedEnvironmentSuggestedValues({
2398
+ scope,
2399
+ purpose: "config",
2400
+ deployConfig: registry.context.deployConfig,
2401
+ tenantConfig: registry.context.tenantConfig,
2402
+ plugins: registry.context.plugins,
2403
+ values: seedValues
2404
+ });
2189
2405
  const validation = validateTreeseedEnvironmentValues({
2190
- values: resolveTreeseedMachineEnvironmentValues(tenantRoot, scope),
2406
+ values: {
2407
+ ...suggestedValues,
2408
+ ...seedValues
2409
+ },
2191
2410
  scope,
2192
2411
  purpose: "config",
2193
2412
  deployConfig: registry.context.deployConfig,
@@ -2197,21 +2416,37 @@ function finalizeTreeseedConfig({
2197
2416
  summary.validationByScope[scope] = validation;
2198
2417
  if (checkConnections) {
2199
2418
  progress(`Checking provider connectivity for ${scope}...`);
2200
- summary.connectionChecks.push(checkTreeseedProviderConnections({ tenantRoot, scope, env }));
2419
+ summary.connectionChecks.push(await checkTreeseedProviderConnections({ tenantRoot, scope, env: seedValues }));
2201
2420
  }
2202
2421
  }
2203
2422
  for (const scope of scopes) {
2204
- summary.readinessByScope[scope] = summarizePersistentReadiness(
2423
+ if (scope !== "local") {
2424
+ const target = createPersistentDeployTarget(scope);
2425
+ const deployState = loadDeployState(tenantRoot, registry.context.deployConfig, { target });
2426
+ const inventory = buildProvisioningSummary(registry.context.deployConfig, deployState, target);
2427
+ const railwayWorkspace = resolveRailwayWorkspace(scopeSeedValues[scope]);
2428
+ summary.resourceInventoryByScope[scope] = inventory;
2429
+ progress(
2430
+ `Resolved ${scope} resources: deployment=${inventory.identity?.deploymentKey}, pages=${inventory.resources?.pagesProject}, web-domain=${inventory.resources?.webDomain ?? "(none)"}, api-domain=${inventory.resources?.apiDomain ?? "(none)"}, r2=${inventory.resources?.contentBucket}, queue=${inventory.resources?.queue}, d1=${inventory.resources?.database}, railway=${inventory.resources?.railwayProject}, workspace=${railwayWorkspace}.`
2431
+ );
2432
+ }
2433
+ summary.readinessByScope[scope] = await summarizePersistentReadiness(
2205
2434
  tenantRoot,
2206
2435
  scope,
2207
2436
  summary.validationByScope[scope],
2208
- summary.connectionChecks.find((report) => report.scope === scope)?.checks ?? []
2437
+ summary.connectionChecks.find((report) => report.scope === scope)?.checks ?? [],
2438
+ scopeSeedValues[scope],
2439
+ { includeReconcileStatus: !initializePersistent }
2209
2440
  );
2210
2441
  }
2211
2442
  const invalidScopes = scopes.filter((scope) => summary.validationByScope[scope]?.ok !== true);
2212
2443
  if (invalidScopes.length > 0) {
2213
2444
  throw new Error(formatTreeseedConfigValidationFailure(summary.validationByScope, scopes));
2214
2445
  }
2446
+ const failingConnectionReports = summary.connectionChecks.filter((report) => report.ok !== true);
2447
+ if (failingConnectionReports.length > 0) {
2448
+ throw new Error(formatTreeseedProviderConnectionFailures(failingConnectionReports));
2449
+ }
2215
2450
  progress("Syncing managed service settings from treeseed.site.yaml...");
2216
2451
  syncManagedServiceSettingsFromDeployConfig(tenantRoot);
2217
2452
  if (initializePersistent) {
@@ -2219,34 +2454,110 @@ function finalizeTreeseedConfig({
2219
2454
  if (scope === "local") {
2220
2455
  continue;
2221
2456
  }
2222
- progress(`Initializing persistent ${scope} environment resources...`);
2457
+ progress(`Deriving desired units for ${scope}...`);
2458
+ const initialized = await reconcileTreeseedTarget({
2459
+ tenantRoot,
2460
+ target: createPersistentDeployTarget(scope),
2461
+ env: scopeSeedValues[scope],
2462
+ write: progress
2463
+ });
2464
+ summary.reconciled.push({
2465
+ scope,
2466
+ target: scope,
2467
+ units: initialized.units.length,
2468
+ actions: initialized.results.map((result) => ({
2469
+ unitId: result.unit.unitId,
2470
+ unitType: result.unit.unitType,
2471
+ provider: result.unit.provider,
2472
+ action: result.action,
2473
+ verified: result.verification?.verified === true,
2474
+ missing: result.verification?.missing ?? [],
2475
+ drifted: result.verification?.drifted ?? []
2476
+ }))
2477
+ });
2478
+ if (scope === "staging") {
2479
+ progress(`Ensuring ${STAGING_BRANCH} exists on origin from ${PRODUCTION_BRANCH}...`);
2480
+ const repository = maybeResolveGitHubRepositorySlug(tenantRoot);
2481
+ if (!repository) {
2482
+ throw new Error("Unable to determine the GitHub repository from the origin remote for staging branch bootstrap.");
2483
+ }
2484
+ const branchBootstrap = await ensureGitHubBranchFromBase(repository, STAGING_BRANCH, {
2485
+ baseBranch: PRODUCTION_BRANCH,
2486
+ client: createGitHubApiClient({
2487
+ env: scopeSeedValues[scope]
2488
+ })
2489
+ });
2490
+ summary.deployed.push({
2491
+ scope,
2492
+ branchBootstrap,
2493
+ result: {}
2494
+ });
2495
+ }
2496
+ progress(`Deploying ${scope}...`);
2223
2497
  applyTreeseedEnvironmentToProcess({ tenantRoot, scope, override: true });
2224
- const initialized = initializeTreeseedPersistentEnvironment({ tenantRoot, scope });
2225
- summary.initialized.push({
2498
+ process.env.TREESEED_RAILWAY_WORKSPACE = process.env.TREESEED_RAILWAY_WORKSPACE || scopeSeedValues[scope].TREESEED_RAILWAY_WORKSPACE || resolveRailwayWorkspace(scopeSeedValues[scope]);
2499
+ const { deployProjectPlatform } = await import("./project-platform.js");
2500
+ const deployResult = await deployProjectPlatform({
2501
+ tenantRoot,
2226
2502
  scope,
2227
- secrets: initialized.secrets.length,
2228
- target: initialized.summary.target
2503
+ skipProvision: true
2229
2504
  });
2505
+ const deployEntry = summary.deployed.find((entry) => entry.scope === scope);
2506
+ if (deployEntry) {
2507
+ deployEntry.result = deployResult;
2508
+ } else {
2509
+ summary.deployed.push({
2510
+ scope,
2511
+ branchBootstrap: null,
2512
+ result: deployResult
2513
+ });
2514
+ }
2515
+ progress(`Re-verifying ${scope} after deployment...`);
2516
+ const finalized = await reconcileTreeseedTarget({
2517
+ tenantRoot,
2518
+ target: createPersistentDeployTarget(scope),
2519
+ env: scopeSeedValues[scope],
2520
+ write: progress
2521
+ });
2522
+ const index = summary.reconciled.findIndex((entry) => entry.scope === scope);
2523
+ const nextSummary = {
2524
+ scope,
2525
+ target: scope,
2526
+ units: finalized.units.length,
2527
+ actions: finalized.results.map((result) => ({
2528
+ unitId: result.unit.unitId,
2529
+ unitType: result.unit.unitType,
2530
+ provider: result.unit.provider,
2531
+ action: result.action,
2532
+ verified: result.verification?.verified === true,
2533
+ missing: result.verification?.missing ?? [],
2534
+ drifted: result.verification?.drifted ?? []
2535
+ }))
2536
+ };
2537
+ if (index >= 0) {
2538
+ summary.reconciled[index] = nextSummary;
2539
+ } else {
2540
+ summary.reconciled.push(nextSummary);
2541
+ }
2230
2542
  }
2231
2543
  }
2232
2544
  if (sync === "github" || sync === "all") {
2233
2545
  progress(`Syncing GitHub environment for ${scopes.at(-1) ?? "prod"}...`);
2234
- summary.synced.github = syncTreeseedGitHubEnvironment({ tenantRoot, scope: scopes.at(-1) ?? "prod" });
2235
- }
2236
- if (sync === "cloudflare" || sync === "all") {
2237
- progress(`Syncing Cloudflare environment for ${scopes.at(-1) ?? "prod"}...`);
2238
- summary.synced.cloudflare = syncTreeseedCloudflareEnvironment({ tenantRoot, scope: scopes.at(-1) ?? "prod" });
2239
- }
2240
- if (sync === "railway" || sync === "all") {
2241
- progress(`Syncing Railway environment for ${scopes.at(-1) ?? "prod"}...`);
2242
- summary.synced.railway = syncTreeseedRailwayEnvironment({ tenantRoot, scope: scopes.at(-1) ?? "prod" });
2546
+ summary.synced.github = await syncTreeseedGitHubEnvironment({ tenantRoot, scope: scopes.at(-1) ?? "prod" });
2243
2547
  }
2244
2548
  for (const scope of scopes) {
2245
- summary.readinessByScope[scope] = summarizePersistentReadiness(
2549
+ const reconciled = summary.reconciled.find((entry) => entry.scope === scope) ?? null;
2550
+ summary.readinessByScope[scope] = initializePersistent && reconciled ? summarizeReconciledPersistentReadiness(
2551
+ scope,
2552
+ summary.validationByScope[scope],
2553
+ summary.connectionChecks.find((report) => report.scope === scope)?.checks ?? [],
2554
+ reconciled
2555
+ ) : await summarizePersistentReadiness(
2246
2556
  tenantRoot,
2247
2557
  scope,
2248
2558
  summary.validationByScope[scope],
2249
- summary.connectionChecks.find((report) => report.scope === scope)?.checks ?? []
2559
+ summary.connectionChecks.find((report) => report.scope === scope)?.checks ?? [],
2560
+ env
2250
2561
  );
2251
2562
  }
2252
2563
  return summary;
@@ -2304,6 +2615,8 @@ export {
2304
2615
  getTreeseedRemoteAuthPaths,
2305
2616
  initializeTreeseedPersistentEnvironment,
2306
2617
  inspectTreeseedKeyAgentStatus,
2618
+ inspectTreeseedKeyAgentTransportDiagnostic,
2619
+ inspectTreeseedPassphraseEnvDiagnostic,
2307
2620
  listDeprecatedTreeseedLocalEnvFiles,
2308
2621
  listRelevantTreeseedConfigEntries,
2309
2622
  loadTreeseedMachineConfig,