@treeseed/sdk 0.10.28 → 0.11.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 (148) hide show
  1. package/README.md +207 -6
  2. package/dist/capacity-provider.d.ts +3 -1
  3. package/dist/capacity-provider.js +25 -5
  4. package/dist/control-plane.d.ts +1 -0
  5. package/dist/control-plane.js +38 -13
  6. package/dist/db/market-schema.d.ts +8860 -6172
  7. package/dist/db/market-schema.js +108 -0
  8. package/dist/db/node-sqlite.js +7 -2
  9. package/dist/hosting/apps.d.ts +12 -0
  10. package/dist/hosting/apps.js +107 -0
  11. package/dist/hosting/builtins.d.ts +25 -0
  12. package/dist/hosting/builtins.js +791 -0
  13. package/dist/hosting/contracts.d.ts +207 -0
  14. package/dist/hosting/contracts.js +0 -0
  15. package/dist/hosting/graph.d.ts +192 -0
  16. package/dist/hosting/graph.js +1106 -0
  17. package/dist/hosting/index.d.ts +4 -0
  18. package/dist/hosting/index.js +4 -0
  19. package/dist/index.d.ts +10 -3
  20. package/dist/index.js +63 -6
  21. package/dist/managed-dependencies.js +1 -2
  22. package/dist/market-client.d.ts +63 -3
  23. package/dist/market-client.js +83 -11
  24. package/dist/operations/services/bootstrap-runner.d.ts +3 -1
  25. package/dist/operations/services/bootstrap-runner.js +22 -2
  26. package/dist/operations/services/config-runtime.d.ts +10 -5
  27. package/dist/operations/services/config-runtime.js +209 -66
  28. package/dist/operations/services/deploy.d.ts +70 -7
  29. package/dist/operations/services/deploy.js +579 -64
  30. package/dist/operations/services/deployment-readiness.d.ts +30 -0
  31. package/dist/operations/services/deployment-readiness.js +175 -0
  32. package/dist/operations/services/git-workflow.d.ts +2 -1
  33. package/dist/operations/services/git-workflow.js +9 -3
  34. package/dist/operations/services/github-actions-verification.d.ts +1 -0
  35. package/dist/operations/services/github-actions-verification.js +1 -0
  36. package/dist/operations/services/github-api.js +1 -1
  37. package/dist/operations/services/github-automation.d.ts +1 -1
  38. package/dist/operations/services/github-automation.js +4 -3
  39. package/dist/operations/services/github-credentials.d.ts +13 -0
  40. package/dist/operations/services/github-credentials.js +58 -0
  41. package/dist/operations/services/hosted-service-checks.d.ts +63 -0
  42. package/dist/operations/services/hosted-service-checks.js +327 -0
  43. package/dist/operations/services/hub-provider-launch.js +3 -3
  44. package/dist/operations/services/live-hosted-service-checks.d.ts +25 -0
  45. package/dist/operations/services/live-hosted-service-checks.js +350 -0
  46. package/dist/operations/services/managed-host-security.js +1 -1
  47. package/dist/operations/services/operations-runner-smoke.d.ts +30 -0
  48. package/dist/operations/services/operations-runner-smoke.js +180 -0
  49. package/dist/operations/services/package-adapters.d.ts +95 -0
  50. package/dist/operations/services/package-adapters.js +288 -0
  51. package/dist/operations/services/package-reference-policy.d.ts +1 -0
  52. package/dist/operations/services/package-reference-policy.js +15 -2
  53. package/dist/operations/services/project-platform.d.ts +80 -22
  54. package/dist/operations/services/project-platform.js +49 -8
  55. package/dist/operations/services/project-web-monitor.js +26 -4
  56. package/dist/operations/services/railway-api.d.ts +88 -5
  57. package/dist/operations/services/railway-api.js +626 -35
  58. package/dist/operations/services/railway-deploy.d.ts +46 -40
  59. package/dist/operations/services/railway-deploy.js +261 -293
  60. package/dist/operations/services/release-candidate.d.ts +19 -0
  61. package/dist/operations/services/release-candidate.js +375 -38
  62. package/dist/operations/services/repository-save-orchestrator.d.ts +3 -1
  63. package/dist/operations/services/repository-save-orchestrator.js +279 -66
  64. package/dist/operations/services/runtime-tools.d.ts +1 -0
  65. package/dist/operations/services/runtime-tools.js +10 -9
  66. package/dist/operations/services/verification-cache.d.ts +25 -0
  67. package/dist/operations/services/verification-cache.js +71 -0
  68. package/dist/operations/services/workspace-dependency-mode.js +9 -1
  69. package/dist/operations/services/workspace-save.js +1 -1
  70. package/dist/operations/services/workspace-tools.js +2 -1
  71. package/dist/platform/contracts.d.ts +32 -1
  72. package/dist/platform/deploy-config.js +73 -8
  73. package/dist/platform/env.yaml +163 -35
  74. package/dist/platform/environment.d.ts +1 -0
  75. package/dist/platform/environment.js +74 -5
  76. package/dist/platform/plugin.d.ts +9 -0
  77. package/dist/platform-operation-store.js +2 -2
  78. package/dist/platform-operations.js +1 -1
  79. package/dist/reconcile/bootstrap-systems.js +2 -2
  80. package/dist/reconcile/builtin-adapters.js +372 -189
  81. package/dist/reconcile/contracts.d.ts +9 -5
  82. package/dist/reconcile/desired-state.d.ts +1 -0
  83. package/dist/reconcile/desired-state.js +5 -5
  84. package/dist/reconcile/engine.d.ts +5 -2
  85. package/dist/reconcile/engine.js +53 -32
  86. package/dist/reconcile/index.d.ts +2 -0
  87. package/dist/reconcile/index.js +2 -0
  88. package/dist/reconcile/live-acceptance.d.ts +79 -0
  89. package/dist/reconcile/live-acceptance.js +1615 -0
  90. package/dist/reconcile/platform.d.ts +104 -0
  91. package/dist/reconcile/platform.js +100 -0
  92. package/dist/reconcile/state.js +4 -4
  93. package/dist/reconcile/units.js +2 -2
  94. package/dist/scripts/deployment-readiness.js +20 -0
  95. package/dist/scripts/generate-treedx-openapi-types.js +186 -0
  96. package/dist/scripts/operations-runner-smoke.js +16 -0
  97. package/dist/scripts/release-verify.js +4 -1
  98. package/dist/scripts/tenant-workflow-action.js +10 -1
  99. package/dist/sdk-types.d.ts +169 -4
  100. package/dist/sdk-types.js +20 -2
  101. package/dist/sdk.d.ts +35 -24
  102. package/dist/sdk.js +186 -17
  103. package/dist/template-launch-requirements.js +9 -0
  104. package/dist/treedx/adapters.d.ts +6 -0
  105. package/dist/treedx/adapters.js +36 -0
  106. package/dist/treedx/client.d.ts +222 -0
  107. package/dist/treedx/client.js +871 -0
  108. package/dist/treedx/errors.d.ts +13 -0
  109. package/dist/treedx/errors.js +17 -0
  110. package/dist/treedx/federated-client.d.ts +27 -0
  111. package/dist/treedx/federated-client.js +158 -0
  112. package/dist/treedx/generated/openapi-types.d.ts +3558 -0
  113. package/dist/treedx/generated/openapi-types.js +0 -0
  114. package/dist/treedx/graph-adapter.d.ts +33 -0
  115. package/dist/treedx/graph-adapter.js +156 -0
  116. package/dist/treedx/index.d.ts +14 -0
  117. package/dist/treedx/index.js +48 -0
  118. package/dist/treedx/market-integration.d.ts +27 -0
  119. package/dist/treedx/market-integration.js +131 -0
  120. package/dist/treedx/ports.d.ts +166 -0
  121. package/dist/treedx/ports.js +231 -0
  122. package/dist/treedx/query-adapter.d.ts +19 -0
  123. package/dist/treedx/query-adapter.js +62 -0
  124. package/dist/treedx/registry-client.d.ts +11 -0
  125. package/dist/treedx/registry-client.js +19 -0
  126. package/dist/treedx/repository-adapter.d.ts +45 -0
  127. package/dist/treedx/repository-adapter.js +308 -0
  128. package/dist/treedx/sdk-integration.d.ts +27 -0
  129. package/dist/treedx/sdk-integration.js +63 -0
  130. package/dist/treedx/types.d.ts +1084 -0
  131. package/dist/treedx/types.js +8 -0
  132. package/dist/treedx/workspace-adapter.d.ts +27 -0
  133. package/dist/treedx/workspace-adapter.js +65 -0
  134. package/dist/treedx-backends.d.ts +218 -0
  135. package/dist/treedx-backends.js +632 -0
  136. package/dist/treedx-client.d.ts +86 -0
  137. package/dist/treedx-client.js +175 -0
  138. package/dist/treeseed/template-catalog/catalog.fixture.json +23 -23
  139. package/dist/workflow/operations.d.ts +119 -13
  140. package/dist/workflow/operations.js +309 -53
  141. package/dist/workflow-state.d.ts +13 -0
  142. package/dist/workflow-state.js +43 -26
  143. package/dist/workflow-support.d.ts +11 -3
  144. package/dist/workflow-support.js +67 -3
  145. package/dist/workflow.d.ts +5 -0
  146. package/drizzle/market/0004_treedx_market_integration.sql +99 -0
  147. package/package.json +34 -3
  148. package/templates/github/deploy-web.workflow.yml +39 -6
@@ -18,6 +18,7 @@ import {
18
18
  inspectTreeseedKeyAgentStatus,
19
19
  loadTreeseedMachineConfig,
20
20
  resolveTreeseedLaunchEnvironment,
21
+ resolveTreeseedMachineEnvironmentValues,
21
22
  resolveTreeseedRemoteSession,
22
23
  rotateTreeseedMachineKey,
23
24
  setTreeseedRemoteSession,
@@ -69,6 +70,7 @@ import {
69
70
  syncBranchWithOrigin
70
71
  } from "../operations/services/git-workflow.js";
71
72
  import { resolveGitHubRepositorySlug } from "../operations/services/github-automation.js";
73
+ import { resolveGitHubCredentialForRepository } from "../operations/services/github-credentials.js";
72
74
  import { dispatchGitHubWorkflowRun } from "../operations/services/github-api.js";
73
75
  import {
74
76
  formatGitHubActionsGateFailure,
@@ -106,12 +108,14 @@ import {
106
108
  repositorySaveErrorDetails,
107
109
  runRepositorySaveOrchestrator
108
110
  } from "../operations/services/repository-save-orchestrator.js";
111
+ import { discoverTreeseedPackageAdapters } from "../operations/services/package-adapters.js";
109
112
  import {
110
113
  assertNoInternalDevReferences,
111
114
  cleanupStaleTreeseedDevTags,
112
115
  collectTreeseedDevTagCleanupPlan,
113
116
  collectInternalDevReferenceIssues,
114
117
  devTagFromDependencySpec,
118
+ installableInternalDependencyVersions,
115
119
  normalizeGitRemoteForManifest,
116
120
  rewriteProjectInternalDependenciesToStableVersions
117
121
  } from "../operations/services/package-reference-policy.js";
@@ -130,6 +134,10 @@ import {
130
134
  workspaceRoot
131
135
  } from "../operations/services/workspace-tools.js";
132
136
  import { runTreeseedHostingAudit } from "../operations/services/hosting-audit.js";
137
+ import { collectTreeseedHostedServiceChecks } from "../operations/services/hosted-service-checks.js";
138
+ import { collectTreeseedDeploymentReadiness } from "../operations/services/deployment-readiness.js";
139
+ import { collectTreeseedLiveHostedServiceChecks } from "../operations/services/live-hosted-service-checks.js";
140
+ import { discoverTreeseedApplications } from "../hosting/apps.js";
133
141
  import { resolveTreeseedWorkflowState } from "../workflow-state.js";
134
142
  import { createTreeseedReconcileRegistry, deriveTreeseedDesiredUnits, filterTreeseedDesiredUnitsByBootstrapSystems, planTreeseedReconciliation, resolveTreeseedBootstrapSelection, reconcileTreeseedTarget } from "../reconcile/index.js";
135
143
  import {
@@ -214,8 +222,10 @@ function readPackageScript(root, packageDir, scriptName) {
214
222
  function ensureWorkflowWorkspacePackageArtifacts(root, helpers) {
215
223
  const packages = [
216
224
  { name: "@treeseed/sdk", dir: "packages/sdk", artifacts: ["dist/workflow-support.js", "dist/plugin-default.js", "dist/platform/env.yaml"] },
225
+ { name: "@treeseed/ui", dir: "packages/ui", artifacts: ["dist/index.js"] },
217
226
  { name: "@treeseed/agent", dir: "packages/agent", artifacts: ["dist/api/index.js", "dist/services/worker.js"] },
218
227
  { name: "@treeseed/core", dir: "packages/core", artifacts: ["dist/plugin-default.js"] },
228
+ { name: "@treeseed/admin", dir: "packages/admin", artifacts: ["dist/plugin.js"] },
219
229
  { name: "@treeseed/cli", dir: "packages/cli", artifacts: ["dist/cli/main.js"] }
220
230
  ];
221
231
  for (const entry of packages) {
@@ -307,9 +317,14 @@ function normalizeCiMode(mode, operation) {
307
317
  if (mode === "hosted" || mode === "off") return mode;
308
318
  return operation === "save" ? "off" : "hosted";
309
319
  }
310
- function normalizeSaveCiMode(mode, branch) {
320
+ function normalizeSaveLane(lane) {
321
+ const value = lane ?? process.env.TREESEED_SAVE_LANE;
322
+ return value === "promotion" ? "promotion" : "fast";
323
+ }
324
+ function normalizeSaveCiMode(mode, branch, lane = "fast") {
311
325
  if (mode === "hosted" || mode === "off") return mode;
312
- return branch === STAGING_BRANCH ? "hosted" : "off";
326
+ if (lane === "promotion") return branch === STAGING_BRANCH ? "hosted" : "off";
327
+ return "off";
313
328
  }
314
329
  function normalizeSaveVerifyMode(mode) {
315
330
  switch (mode) {
@@ -329,8 +344,16 @@ function normalizeSaveVerifyMode(mode) {
329
344
  return "skip";
330
345
  }
331
346
  }
332
- function shouldUseHostedSaveCi(input, branch) {
333
- return normalizeSaveCiMode(input.ciMode, branch) === "hosted" || input.verifyMode === "hosted" || input.verifyMode === "both" || input.verifyDeployedResources === true;
347
+ function normalizeReleaseCandidateMode(mode, operation, lane = "fast") {
348
+ const value = mode ?? process.env.TREESEED_RELEASE_CANDIDATE_MODE;
349
+ if (value === "hybrid" || value === "strict" || value === "skip") {
350
+ return value;
351
+ }
352
+ if (operation === "save" && lane === "promotion") return "strict";
353
+ return operation === "save" ? "hybrid" : "strict";
354
+ }
355
+ function shouldUseHostedSaveCi(input, branch, lane = normalizeSaveLane(input.lane)) {
356
+ return normalizeSaveCiMode(input.ciMode, branch, lane) === "hosted" || input.verifyMode === "hosted" || input.verifyMode === "both" || input.verifyDeployedResources === true;
334
357
  }
335
358
  function worktreePayload(root, requestedMode) {
336
359
  const metadata = managedWorkflowWorktreeMetadata(root);
@@ -412,6 +435,7 @@ async function waitForWorkflowGates(operation, gates, ciMode, options = {}) {
412
435
  }
413
436
  const result = await waitForGitHubActionsGate(gate, {
414
437
  operation,
438
+ env: githubWorkflowGateEnv(options.root, gate),
415
439
  onProgress: options.onProgress
416
440
  });
417
441
  const normalized = {
@@ -435,6 +459,23 @@ async function waitForWorkflowGates(operation, gates, ciMode, options = {}) {
435
459
  }
436
460
  return results;
437
461
  }
462
+ function githubWorkflowGateEnv(root, gate) {
463
+ if (!root) return process.env;
464
+ try {
465
+ const repository = gate.repository ?? resolveGitHubRepositorySlug(gate.repoPath);
466
+ const scope = gate.branch === PRODUCTION_BRANCH ? "prod" : "staging";
467
+ const values = resolveTreeseedMachineEnvironmentValues(root, scope);
468
+ const credential = resolveGitHubCredentialForRepository(repository, { values, env: process.env });
469
+ if (!credential.token) return process.env;
470
+ return {
471
+ ...process.env,
472
+ GH_TOKEN: credential.token,
473
+ GITHUB_TOKEN: credential.token
474
+ };
475
+ } catch {
476
+ return process.env;
477
+ }
478
+ }
438
479
  const HOSTED_DEPLOY_GATE_TIMEOUT_SECONDS = 45 * 60;
439
480
  function hostedDeployGate(gate) {
440
481
  return {
@@ -642,24 +683,134 @@ function buildWorkflowResult(operation, cwd, payload, options = {}) {
642
683
  errors: options.errors ?? []
643
684
  };
644
685
  }
645
- async function runReadOnlyHostingAuditForWorkflow(operation, root, helpers, environment, options = { enabled: true }) {
646
- if (!options.enabled) {
647
- return null;
686
+ function parseGitStatusChangedPaths(status) {
687
+ return status.split("\n").map((line) => line.trimEnd()).filter(Boolean).map((line) => {
688
+ const value = line.slice(3).trim();
689
+ return value.includes(" -> ") ? value.split(" -> ").at(-1).trim() : value;
690
+ }).filter(Boolean);
691
+ }
692
+ function availableWorkflowAppIds(root) {
693
+ try {
694
+ const ids = discoverTreeseedApplications(root).map((app) => app.id);
695
+ return ids.length > 0 ? ids : ["web"];
696
+ } catch {
697
+ return ["web"];
698
+ }
699
+ }
700
+ function selectWorkflowApplications(root, input = {}) {
701
+ const available = availableWorkflowAppIds(root);
702
+ const availableSet = new Set(available);
703
+ const selected = /* @__PURE__ */ new Set();
704
+ const reasons = [];
705
+ const add = (appId, reason) => {
706
+ if (!availableSet.has(appId)) return;
707
+ selected.add(appId);
708
+ reasons.push({ appId, reason });
709
+ };
710
+ const packages = [
711
+ ...input.packageSelection?.selected ?? [],
712
+ ...input.packageSelection?.changed ?? [],
713
+ ...input.packageSelection?.dependents ?? []
714
+ ];
715
+ const appByPackage = new Map(discoverTreeseedApplications(root).filter((app) => app.relativeRoot.startsWith("packages/")).map((app) => [`@treeseed/${app.relativeRoot.slice("packages/".length).split("/")[0]}`, app.id]));
716
+ for (const packageName of packages) {
717
+ if (packageName === "@treeseed/api" || packageName === "@treeseed/treedx" || packageName === "@treeseed/agent") {
718
+ add("api", `${packageName} changed`);
719
+ } else if (packageName === "@treeseed/core" || packageName === "@treeseed/ui" || packageName === "@treeseed/admin") {
720
+ add("web", `${packageName} changed`);
721
+ } else if (packageName === "@treeseed/sdk" || packageName === "@treeseed/cli") {
722
+ add("web", `${packageName} is shared`);
723
+ add("api", `${packageName} is shared`);
724
+ }
725
+ const packageAppId = appByPackage.get(packageName);
726
+ if (packageAppId) add(packageAppId, `${packageName} owns ${packageAppId}`);
727
+ }
728
+ const changedPaths = input.changedPaths ?? parseGitStatusChangedPaths(gitStatusPorcelain(root));
729
+ const appByPackagePath = new Map(discoverTreeseedApplications(root).filter((app) => app.relativeRoot.startsWith("packages/")).map((app) => [app.relativeRoot, app.id]));
730
+ for (const file of changedPaths) {
731
+ if (file.startsWith("packages/api/") || file === "packages/api") {
732
+ add("api", `${file} is API-owned`);
733
+ } else if (file.startsWith("packages/treedx/") || file === "packages/treedx") {
734
+ add("api", `${file} is TreeDX implementation`);
735
+ } else if (file.startsWith("packages/core/") || file.startsWith("packages/ui/") || file.startsWith("packages/admin/") || file.startsWith("src/") || file.startsWith("content/") || file.startsWith("public/") || file === "treeseed.site.yaml") {
736
+ add("web", `${file} is web-owned`);
737
+ } else if (file.startsWith("packages/sdk/") || file.startsWith("packages/cli/") || file === "package.json" || file === "package-lock.json" || file.startsWith(".github/")) {
738
+ add("web", `${file} is shared workflow/config`);
739
+ add("api", `${file} is shared workflow/config`);
740
+ }
741
+ for (const [packageRoot, appId] of appByPackagePath) {
742
+ if (file === packageRoot || file.startsWith(`${packageRoot}/`)) {
743
+ add(appId, `${file} is ${appId}-owned`);
744
+ }
745
+ }
746
+ }
747
+ const source = packages.length > 0 ? "package-selection" : changedPaths.length > 0 ? "changed-paths" : "default";
748
+ const finalSelected = selected.size > 0 ? available.filter((appId) => selected.has(appId)) : available;
749
+ return {
750
+ selected: finalSelected,
751
+ skipped: available.filter((appId) => !finalSelected.includes(appId)).map((appId) => ({ appId, reason: "No changed files or selected packages target this application." })),
752
+ reasons,
753
+ source
754
+ };
755
+ }
756
+ function singleSelectedWorkflowAppId(selection) {
757
+ return selection.selected.length === 1 ? selection.selected[0] : void 0;
758
+ }
759
+ async function runWorkflowHostedResourceVerification(operation, root, helpers, environment, options = { enabled: true }) {
760
+ if (!options.enabled) return null;
761
+ const target = environment === "prod" ? "prod" : environment === "local" ? "local" : "staging";
762
+ const readiness = collectTreeseedDeploymentReadiness({
763
+ tenantRoot: root,
764
+ environment: target,
765
+ appId: options.appId
766
+ });
767
+ if (options.strict && !readiness.ok) {
768
+ const failures = readiness.checks.filter((check) => check.status === "failed").map((check) => `${check.id}: ${check.message}${check.remediation ? ` Remediation: ${check.remediation}` : ""}`);
769
+ workflowError(operation, "validation_failed", `Deployment readiness failed for ${target}:
770
+ ${failures.join("\n")}`, {
771
+ details: { readiness }
772
+ });
648
773
  }
649
- helpers.write(`[workflow][hosting-audit] Running read-only hosting audit for ${environment}.`);
650
- const report = await runTreeseedHostingAudit({
774
+ const hostingAudit = await runTreeseedHostingAudit({
651
775
  tenantRoot: root,
652
776
  environment,
653
777
  repair: false,
778
+ hostKinds: options.appId === "api" ? ["repository"] : void 0,
654
779
  env: helpers.context.env,
655
780
  write: (line) => helpers.write(line)
656
781
  });
657
- if (options.strict && !report.ok) {
658
- workflowError(operation, "validation_failed", `Hosting audit failed for ${report.environment}: ${report.blockers.join("\n")}`, {
659
- details: { hostingAudit: report }
782
+ const hostedServices = options.live ? await collectTreeseedLiveHostedServiceChecks({
783
+ tenantRoot: root,
784
+ target,
785
+ strict: options.strict === true,
786
+ requireLiveRailway: options.strict === true,
787
+ requireLiveHttp: options.strict === true,
788
+ appId: options.appId,
789
+ retry: { attempts: 12, intervalMs: 1e4 },
790
+ env: helpers.context.env
791
+ }) : collectTreeseedHostedServiceChecks({
792
+ tenantRoot: root,
793
+ target,
794
+ appId: options.appId
795
+ });
796
+ if (options.strict && !hostingAudit.ok) {
797
+ workflowError(operation, "validation_failed", `Hosting audit failed for ${hostingAudit.environment}: ${hostingAudit.blockers.join("\n")}`, {
798
+ details: { hostingAudit, hostedServices, readiness }
660
799
  });
661
800
  }
662
- return report;
801
+ if (options.strict && hostedServices.summary.failed > 0) {
802
+ const failures = hostedServices.checks.filter((check) => check.status === "failed").map((check) => `${check.id}: ${check.issues.join("; ") || check.description}${check.remediation ? ` Remediation: ${check.remediation}` : ""}`);
803
+ workflowError(operation, "validation_failed", `Hosted service checks failed for ${hostedServices.target}:
804
+ ${failures.join("\n")}`, {
805
+ details: { hostingAudit, hostedServices, readiness }
806
+ });
807
+ }
808
+ return {
809
+ hostingAudit,
810
+ hostedServices,
811
+ readiness,
812
+ live: options.live === true
813
+ };
663
814
  }
664
815
  function normalizeExecutionMode(input) {
665
816
  return input?.plan === true || input?.dryRun === true ? "plan" : "execute";
@@ -710,7 +861,8 @@ function writeJsonFile(path, value) {
710
861
  `, "utf8");
711
862
  }
712
863
  function applyStableWorkspaceVersionChanges(root, versions) {
713
- const stableGitReferences = stablePackageGitReferences(root, versions);
864
+ const dependencyVersions = installableInternalDependencyVersions(root, versions);
865
+ const stableGitReferences = stablePackageGitReferences(root, dependencyVersions);
714
866
  for (const target of [{ name: "@treeseed/market", dir: root }, ...workspacePackages(root).map((pkg) => ({ name: pkg.name, dir: pkg.dir }))]) {
715
867
  const packageJsonPath = resolve(target.dir, "package.json");
716
868
  if (!existsSync(packageJsonPath)) continue;
@@ -724,7 +876,7 @@ function applyStableWorkspaceVersionChanges(root, versions) {
724
876
  for (const field of ["dependencies", "optionalDependencies", "peerDependencies", "devDependencies"]) {
725
877
  const values = packageJson[field];
726
878
  if (!values || typeof values !== "object" || Array.isArray(values)) continue;
727
- for (const [dependencyName, version] of versions.entries()) {
879
+ for (const [dependencyName, version] of dependencyVersions.entries()) {
728
880
  if (!(dependencyName in values)) continue;
729
881
  const dependencySpec = stableGitReferences.get(dependencyName) ?? version;
730
882
  if (String(values[dependencyName]) === dependencySpec) continue;
@@ -1064,7 +1216,7 @@ async function connectTreeseedMarketProject(helpers, tenantRoot, input, context)
1064
1216
  principal: activeRemoteSession?.principal ?? null
1065
1217
  });
1066
1218
  }
1067
- const runnerHostId = `market-runner:${projectId}`;
1219
+ const runnerHostId = `operations-runner:${projectId}`;
1068
1220
  if (connectionResult.runnerToken) {
1069
1221
  setTreeseedRemoteSession(tenantRoot, {
1070
1222
  hostId: runnerHostId,
@@ -1182,14 +1334,34 @@ function findAutoResumableSaveRun(root, branch) {
1182
1334
  }
1183
1335
  return listInterruptedWorkflowRuns(root).find((journal) => journal.command === "save" && journal.resumable && journal.session.branchName === branch) ?? null;
1184
1336
  }
1185
- function gatesForSavedPackageReports(reports) {
1186
- return reports.filter((repo) => repo.pushed && repo.commitSha && repo.branch).map((repo) => ({
1187
- name: repo.name,
1188
- repoPath: repo.path,
1189
- workflow: "verify.yml",
1190
- branch: String(repo.branch),
1191
- headSha: String(repo.commitSha)
1192
- }));
1337
+ function workflowFileExists(repoPath, workflow) {
1338
+ return existsSync(resolve(repoPath, ".github", "workflows", workflow));
1339
+ }
1340
+ function hostedWorkflowForSavedRepository(root, repo) {
1341
+ const adapterByPath = new Map(discoverTreeseedPackageAdapters(root).map((adapter) => [resolve(adapter.dir), adapter]));
1342
+ const adapterWorkflow = packageHostedVerifyWorkflow(adapterByPath.get(resolve(repo.path)));
1343
+ if (adapterWorkflow) return adapterWorkflow;
1344
+ if (repo.branch === STAGING_BRANCH && existsSync(resolve(repo.path, "treeseed.site.yaml")) && workflowFileExists(repo.path, "deploy.yml")) {
1345
+ return "deploy.yml";
1346
+ }
1347
+ return "verify.yml";
1348
+ }
1349
+ function gatesForSavedRepositoryReports(root, reports) {
1350
+ return reports.filter((repo) => repo.pushed && repo.commitSha && repo.branch && (repo.committed || repo.tagName)).map((repo) => {
1351
+ const workflow = hostedWorkflowForSavedRepository(root, repo);
1352
+ const gate = {
1353
+ name: repo.name,
1354
+ repoPath: repo.path,
1355
+ workflow,
1356
+ branch: String(repo.branch),
1357
+ headSha: String(repo.commitSha)
1358
+ };
1359
+ return /^deploy(?:[-.]|$)/u.test(workflow) ? hostedDeployGate(gate) : gate;
1360
+ });
1361
+ }
1362
+ function packageHostedVerifyWorkflow(adapter) {
1363
+ const workflow = adapter?.metadata?.hostedVerifyWorkflow;
1364
+ return typeof workflow === "string" && workflow.trim() ? workflow.trim().replace(/^\.github\/workflows\//u, "") : null;
1193
1365
  }
1194
1366
  function gateForSavedRootReport(report, branch, scope) {
1195
1367
  if (!branch || scope === "local" || !report.pushed || !report.commitSha) {
@@ -1779,13 +1951,15 @@ async function runReleaseCandidateForPlan(operation, root, plannedRelease, optio
1779
1951
  root,
1780
1952
  plannedVersions: { ...stableDependencyVersions, ...plannedVersions },
1781
1953
  selectedPackageNames: packageSelection.selected,
1782
- allowReuse: options.allowReuse
1954
+ allowReuse: options.allowReuse,
1955
+ mode: options.mode ?? normalizeReleaseCandidateMode(void 0, operation)
1783
1956
  });
1784
1957
  assertReleaseCandidatePassed(operation, report);
1785
1958
  return report;
1786
1959
  }
1787
1960
  function buildReleasePlanSnapshot(input) {
1788
1961
  const selectedPackageNames = new Set(input.packageSelection.selected);
1962
+ const applicationSelection = selectWorkflowApplications(input.root, { packageSelection: input.packageSelection });
1789
1963
  const versionPlan = planWorkspaceReleaseBump(input.level, input.root, input.mode === "recursive-workspace" ? { selectedPackageNames, repairVersionLine: input.repairVersionLine === true, targetVersionLine: input.targetVersionLine } : {});
1790
1964
  const plannedSelected = [...versionPlan.selected].filter((name) => versionPlan.versions.has(name));
1791
1965
  const plannedChanged = input.repairVersionLine === true ? plannedSelected : Array.from(new Set(input.packageSelection.changed.filter((name) => plannedSelected.includes(name))));
@@ -1821,6 +1995,7 @@ function buildReleasePlanSnapshot(input) {
1821
1995
  packageSelection: plannedPackageSelection,
1822
1996
  plannedVersions,
1823
1997
  stableDependencyVersions,
1998
+ applicationSelection,
1824
1999
  plannedDevReferenceRewrites,
1825
2000
  plannedPublishWaits: plannedPackageSelection.selected.map((name) => ({
1826
2001
  name,
@@ -2554,6 +2729,7 @@ async function workflowConfig(helpers, input = {}) {
2554
2729
  sync,
2555
2730
  env: helpers.context.env,
2556
2731
  checkConnections: bootstrapOnly || sync !== "none" || scopes.some((scope) => scope !== "local"),
2732
+ initializePersistent: bootstrapOnly,
2557
2733
  systems: bootstrapSystemsInput,
2558
2734
  skipUnavailable,
2559
2735
  bootstrapExecution,
@@ -2916,6 +3092,9 @@ async function workflowSave(helpers, input) {
2916
3092
  const planAutoResumeRun = executionMode === "plan" ? findAutoResumableSaveRun(root, branch) : null;
2917
3093
  const effectiveInput = autoResumeRun ? autoResumeRun.input : input;
2918
3094
  const message = String(effectiveInput.message ?? "").trim();
3095
+ const saveLane = normalizeSaveLane(effectiveInput.lane);
3096
+ const saveCiMode = normalizeSaveCiMode(effectiveInput.ciMode, branch, saveLane);
3097
+ const releaseCandidateMode = normalizeReleaseCandidateMode(effectiveInput.releaseCandidate, "save", saveLane);
2919
3098
  const optionsHotfix = effectiveInput.hotfix === true;
2920
3099
  const previewInitialized = branchPreviewInitialized(root, branch);
2921
3100
  applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope, override: true });
@@ -2948,6 +3127,7 @@ async function workflowSave(helpers, input) {
2948
3127
  verifyMode: normalizeSaveVerifyMode(effectiveInput.verify === false ? "skip" : effectiveInput.verifyMode),
2949
3128
  commitMessageMode: effectiveInput.commitMessageMode ?? "auto"
2950
3129
  });
3130
+ const applicationSelection = selectWorkflowApplications(root);
2951
3131
  const workspaceLinks = inspectWorkspaceDependencyMode(root, { mode: effectiveInput.workspaceLinks ?? "auto", env: helpers.context.env });
2952
3132
  return buildWorkflowResult(
2953
3133
  "save",
@@ -2967,8 +3147,11 @@ async function workflowSave(helpers, input) {
2967
3147
  failure: planAutoResumeRun.failure
2968
3148
  } : null,
2969
3149
  workspaceLinks,
2970
- ciMode: normalizeSaveCiMode(effectiveInput.ciMode, branch),
3150
+ ciMode: saveCiMode,
3151
+ lane: saveLane,
2971
3152
  verifyMode: effectiveInput.verifyMode ?? "fast",
3153
+ releaseCandidateMode,
3154
+ applicationSelection,
2972
3155
  ...worktreePayload(root, effectiveInput.worktreeMode),
2973
3156
  repositoryPlan,
2974
3157
  waves: repositoryPlan.waves,
@@ -2977,8 +3160,8 @@ async function workflowSave(helpers, input) {
2977
3160
  { id: "workspace-unlink", description: "Remove local workspace links before deployment install and lockfile updates" },
2978
3161
  ...repositoryPlan.plannedSteps,
2979
3162
  { id: "lockfile-validation", description: "Validate refreshed package-lock.json files before any save commit is pushed" },
2980
- ...shouldUseHostedSaveCi(effectiveInput, branch) ? [{ id: "hosted-ci", description: `Wait for hosted save workflows on ${branch}` }] : [],
2981
- ...branch === STAGING_BRANCH ? [{ id: "release-candidate", description: "Run release-candidate readiness checks for the saved staging state" }] : [],
3163
+ ...shouldUseHostedSaveCi(effectiveInput, branch, saveLane) ? [{ id: "hosted-ci", description: `Wait for hosted save workflows on ${branch}` }] : [],
3164
+ ...branch === STAGING_BRANCH ? [{ id: "release-candidate", description: `Run ${releaseCandidateMode} release-candidate readiness checks for the saved staging state` }] : [],
2982
3165
  { id: "workspace-link", description: "Restore local workspace links after save" },
2983
3166
  ...beforeState.branchRole === "feature" && (effectiveInput.preview === true || previewInitialized) ? [{ id: "preview", description: `Refresh preview deployment for ${branch}` }] : []
2984
3167
  ]
@@ -3014,10 +3197,12 @@ async function workflowSave(helpers, input) {
3014
3197
  gitDependencyProtocol: effectiveInput.gitDependencyProtocol ?? "preserve-origin",
3015
3198
  gitRemoteWriteMode: effectiveInput.gitRemoteWriteMode ?? "ssh-pushurl",
3016
3199
  verifyMode: effectiveInput.verifyMode ?? (effectiveInput.verify === false ? "skip" : "fast"),
3017
- ciMode: effectiveInput.ciMode ?? (branch === STAGING_BRANCH ? "hosted" : "auto"),
3200
+ ciMode: saveCiMode,
3201
+ lane: saveLane,
3018
3202
  worktreeMode: effectiveInput.worktreeMode ?? "auto",
3019
3203
  commitMessageMode: effectiveInput.commitMessageMode ?? "auto",
3020
3204
  workspaceLinks: effectiveInput.workspaceLinks ?? "auto",
3205
+ releaseCandidate: releaseCandidateMode,
3021
3206
  verifyDeployedResources: effectiveInput.verifyDeployedResources === true
3022
3207
  },
3023
3208
  [
@@ -3029,7 +3214,7 @@ async function workflowSave(helpers, input) {
3029
3214
  branch,
3030
3215
  resumable: true
3031
3216
  },
3032
- ...shouldUseHostedSaveCi(effectiveInput, branch) ? [{
3217
+ ...shouldUseHostedSaveCi(effectiveInput, branch, saveLane) ? [{
3033
3218
  id: "hosted-ci",
3034
3219
  description: `Wait for hosted save workflows on ${branch}`,
3035
3220
  repoName: rootRepo.name,
@@ -3085,19 +3270,19 @@ async function workflowSave(helpers, input) {
3085
3270
  commitMessageMode: effectiveInput.commitMessageMode ?? "auto",
3086
3271
  workflowRunId: workflowRun.runId,
3087
3272
  onProgress: (line, stream) => helpers.write(line, stream),
3088
- onWaveSaved: branch === STAGING_BRANCH && shouldUseHostedSaveCi(effectiveInput, branch) ? async ({ nodes, reports, rootRepo: waveRootRepo }) => {
3089
- const packageReportsForWave = reports.filter((repo, index) => nodes[index]?.kind === "package");
3090
- const rootReportForWave = nodes.some((node) => node.kind === "project") ? waveRootRepo : null;
3273
+ onWaveSaved: branch === STAGING_BRANCH && shouldUseHostedSaveCi(effectiveInput, branch, saveLane) ? async ({ nodes, reports, rootRepo: waveRootRepo }) => {
3274
+ const nonRootReportsForWave = reports.filter((repo, index) => nodes[index]?.id !== ".");
3275
+ const rootReportForWave = nodes.some((node) => node.id === ".") ? waveRootRepo : null;
3091
3276
  const gates = [
3092
- ...gatesForSavedPackageReports(packageReportsForWave),
3277
+ ...gatesForSavedRepositoryReports(root, nonRootReportsForWave),
3093
3278
  ...rootReportForWave ? gateForSavedRootReport(rootReportForWave, branch, scope) : []
3094
3279
  ];
3095
3280
  if (gates.length === 0) {
3096
3281
  return [];
3097
3282
  }
3098
- const packageNames = packageReportsForWave.map((repo) => repo.name).join(", ");
3099
- if (packageNames) {
3100
- helpers.write(`[save][workflow] Waiting for hosted package gates before saving dependents: ${packageNames}.`);
3283
+ const repositoryNames = gates.map((gate) => gate.name).join(", ");
3284
+ if (nonRootReportsForWave.length > 0) {
3285
+ helpers.write(`[save][workflow] Waiting for hosted repository gates before saving dependents: ${repositoryNames}.`);
3101
3286
  } else if (rootReportForWave) {
3102
3287
  helpers.write("[save][workflow] Waiting for hosted market deploy gate.");
3103
3288
  }
@@ -3131,7 +3316,7 @@ async function workflowSave(helpers, input) {
3131
3316
  lockfileValidation: repo.lockfileValidation
3132
3317
  }))
3133
3318
  };
3134
- const saveWorkflowGates = shouldUseHostedSaveCi(effectiveInput, branch) ? await executeJournalStep(root, workflowRun.runId, "hosted-ci", async () => {
3319
+ const saveWorkflowGates = shouldUseHostedSaveCi(effectiveInput, branch, saveLane) ? await executeJournalStep(root, workflowRun.runId, "hosted-ci", async () => {
3135
3320
  if (branch === STAGING_BRANCH) {
3136
3321
  const workflowGates = saveResult?.workflowGates ?? [];
3137
3322
  if (effectiveInput.verifyDeployedResources !== true || scope === "local" || !savedRootRepo.commitSha) {
@@ -3200,7 +3385,7 @@ async function workflowSave(helpers, input) {
3200
3385
  }).then((workflowGates) => ({ workflowGates }));
3201
3386
  }) : { workflowGates: [] };
3202
3387
  const releaseCandidate = branch === STAGING_BRANCH ? await executeJournalStep(root, workflowRun.runId, "release-candidate", () => {
3203
- helpers.write("[save][workflow] Running staging release-candidate readiness checks.");
3388
+ helpers.write(`[save][workflow] Running staging release-candidate readiness checks (${releaseCandidateMode}).`);
3204
3389
  const releaseSession = resolveTreeseedWorkflowSession(root);
3205
3390
  const stagingReleasePlan = buildReleasePlanSnapshot({
3206
3391
  root,
@@ -3211,7 +3396,7 @@ async function workflowSave(helpers, input) {
3211
3396
  rootRepo: savedRootRepo,
3212
3397
  blockers: []
3213
3398
  });
3214
- return runReleaseCandidateForPlan("save", root, stagingReleasePlan);
3399
+ return runReleaseCandidateForPlan("save", root, stagingReleasePlan, { mode: releaseCandidateMode });
3215
3400
  }) : null;
3216
3401
  let previewAction = { status: "skipped" };
3217
3402
  if (beforeState.branchRole === "feature" && branch) {
@@ -3227,12 +3412,22 @@ async function workflowSave(helpers, input) {
3227
3412
  };
3228
3413
  }
3229
3414
  }
3230
- const hostingAudit = await runReadOnlyHostingAuditForWorkflow(
3415
+ const applicationSelection = selectWorkflowApplications(root, {
3416
+ packageSelection: {
3417
+ selected: savedPackageReports.map((report) => report.name)
3418
+ }
3419
+ });
3420
+ const hostingAudit = await runWorkflowHostedResourceVerification(
3231
3421
  "save",
3232
3422
  root,
3233
3423
  helpers,
3234
3424
  scope === "prod" ? "prod" : scope === "local" ? "local" : "staging",
3235
- { enabled: effectiveInput.verifyDeployedResources === true, strict: true }
3425
+ {
3426
+ enabled: effectiveInput.verifyDeployedResources === true,
3427
+ strict: true,
3428
+ live: effectiveInput.verifyDeployedResources === true,
3429
+ appId: singleSelectedWorkflowAppId(applicationSelection)
3430
+ }
3236
3431
  );
3237
3432
  const payload = {
3238
3433
  mode: saveResult?.mode ?? mode,
@@ -3257,8 +3452,11 @@ async function workflowSave(helpers, input) {
3257
3452
  workspaceLinks,
3258
3453
  commandReadiness,
3259
3454
  lockfileValidation,
3260
- ciMode: normalizeSaveCiMode(effectiveInput.ciMode, branch),
3455
+ ciMode: saveCiMode,
3456
+ lane: saveLane,
3261
3457
  verifyMode: effectiveInput.verifyMode ?? "fast",
3458
+ releaseCandidateMode,
3459
+ applicationSelection,
3262
3460
  workflowGates: saveWorkflowGates?.workflowGates ?? [],
3263
3461
  releaseCandidate,
3264
3462
  hostingAudit,
@@ -3529,6 +3727,13 @@ async function workflowStage(helpers, input) {
3529
3727
  } catch (error) {
3530
3728
  blockers.push(error instanceof Error ? error.message : String(error));
3531
3729
  }
3730
+ const applicationSelection = selectWorkflowApplications(root, { packageSelection: initialSession.packageSelection });
3731
+ const readiness = collectTreeseedDeploymentReadiness({
3732
+ tenantRoot: root,
3733
+ environment: "staging",
3734
+ appId: singleSelectedWorkflowAppId(applicationSelection)
3735
+ });
3736
+ blockers.push(...readiness.checks.filter((check) => check.status === "failed").map((check) => `${check.id}: ${check.message}${check.remediation ? ` Remediation: ${check.remediation}` : ""}`));
3532
3737
  return buildWorkflowResult(
3533
3738
  "stage",
3534
3739
  root,
@@ -3539,6 +3744,7 @@ async function workflowStage(helpers, input) {
3539
3744
  mergeStrategy: "squash",
3540
3745
  message,
3541
3746
  ciMode,
3747
+ applicationSelection,
3542
3748
  autoResumeCandidate: planAutoResumeRun ? {
3543
3749
  runId: planAutoResumeRun.runId,
3544
3750
  branch: planAutoResumeRun.session.branchName,
@@ -3547,6 +3753,7 @@ async function workflowStage(helpers, input) {
3547
3753
  ...worktreePayload(root, effectiveInput.worktreeMode),
3548
3754
  autoSaveRequired: initialSession.rootRepo.dirty || initialSession.packageRepos.some((repo) => repo.dirty),
3549
3755
  blockers,
3756
+ readiness,
3550
3757
  rootRepo: createWorkspaceRootRepoReport(root),
3551
3758
  repos: createWorkspacePackageReports(root),
3552
3759
  plannedSteps: [
@@ -3593,6 +3800,16 @@ async function workflowStage(helpers, input) {
3593
3800
  applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope: "staging", override: true });
3594
3801
  validateStagingWorkflowContracts(root);
3595
3802
  runWorkspaceSavePreflight({ cwd: root });
3803
+ const stagingReadiness = collectTreeseedDeploymentReadiness({ tenantRoot: root, environment: "staging" });
3804
+ if (!stagingReadiness.ok) {
3805
+ workflowError(
3806
+ "stage",
3807
+ "validation_failed",
3808
+ `Deployment readiness failed for staging:
3809
+ ${stagingReadiness.checks.filter((check) => check.status === "failed").map((check) => `${check.id}: ${check.message}${check.remediation ? ` Remediation: ${check.remediation}` : ""}`).join("\n")}`,
3810
+ { details: { readiness: stagingReadiness } }
3811
+ );
3812
+ }
3596
3813
  const repoDir = session.gitRoot;
3597
3814
  const rootRepo = createWorkspaceRootRepoReport(root);
3598
3815
  const packageReports = createWorkspacePackageReports(root);
@@ -3759,12 +3976,18 @@ async function workflowStage(helpers, input) {
3759
3976
  blockers: []
3760
3977
  });
3761
3978
  const releaseCandidate = await executeJournalStep(root, workflowRun.runId, "release-candidate", () => runReleaseCandidateForPlan("stage", root, stageReleasePlan));
3762
- const hostingAudit = await runReadOnlyHostingAuditForWorkflow(
3979
+ const applicationSelection = selectWorkflowApplications(root, { packageSelection: session.packageSelection });
3980
+ const hostingAudit = await runWorkflowHostedResourceVerification(
3763
3981
  "stage",
3764
3982
  root,
3765
3983
  helpers,
3766
3984
  "staging",
3767
- { enabled: effectiveInput.verifyDeployedResources === true, strict: true }
3985
+ {
3986
+ enabled: effectiveInput.verifyDeployedResources === true,
3987
+ strict: true,
3988
+ live: effectiveInput.verifyDeployedResources === true,
3989
+ appId: singleSelectedWorkflowAppId(applicationSelection)
3990
+ }
3768
3991
  );
3769
3992
  const previewCleanup = effectiveInput.deletePreview === false ? (skipJournalStep(root, workflowRun.runId, "preview-cleanup", { performed: false }), { performed: false }) : await executeJournalStep(root, workflowRun.runId, "preview-cleanup", () => destroyPreviewIfPresent(root, featureBranch));
3770
3993
  const rootCleanup = await executeJournalStep(root, workflowRun.runId, "cleanup-root", () => {
@@ -3813,6 +4036,7 @@ async function workflowStage(helpers, input) {
3813
4036
  rootRepo,
3814
4037
  stagingWait,
3815
4038
  releaseCandidate,
4039
+ applicationSelection,
3816
4040
  previewCleanup,
3817
4041
  lockfileValidation: rootMerge?.lockfileValidation ?? null,
3818
4042
  lockfileInstall: rootMerge?.lockfileInstall ?? null,
@@ -3923,10 +4147,19 @@ async function workflowRelease(helpers, input) {
3923
4147
  level,
3924
4148
  repairVersionLine: effectiveInput.repairVersionLine === true
3925
4149
  });
4150
+ const plannedReadiness = collectTreeseedDeploymentReadiness({
4151
+ tenantRoot: root,
4152
+ environment: "prod",
4153
+ appId: singleSelectedWorkflowAppId(plannedRelease.applicationSelection)
4154
+ });
4155
+ if (!isResume) {
4156
+ blockers.push(...plannedReadiness.checks.filter((check) => check.status === "failed").map((check) => `${check.id}: ${check.message}${check.remediation ? ` Remediation: ${check.remediation}` : ""}`));
4157
+ }
3926
4158
  plannedRelease.blockers = blockers;
3927
4159
  if (executionMode === "plan") {
3928
4160
  return buildWorkflowResult("release", root, {
3929
4161
  ...plannedRelease,
4162
+ readiness: plannedReadiness,
3930
4163
  ciMode,
3931
4164
  fresh: input.fresh === true,
3932
4165
  freshArchivedRuns: [],
@@ -4011,6 +4244,16 @@ async function workflowRelease(helpers, input) {
4011
4244
  const rootVersion = String(releasePlan.rootVersion);
4012
4245
  applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope: "staging", override: true });
4013
4246
  assertReleaseGitHubAutomationReady(root, effectiveSelectedPackageNames, ciMode);
4247
+ const productionReadiness = collectTreeseedDeploymentReadiness({ tenantRoot: root, environment: "prod" });
4248
+ if (!productionReadiness.ok) {
4249
+ workflowError(
4250
+ "release",
4251
+ "validation_failed",
4252
+ `Deployment readiness failed for prod:
4253
+ ${productionReadiness.checks.filter((check) => check.status === "failed").map((check) => `${check.id}: ${check.message}${check.remediation ? ` Remediation: ${check.remediation}` : ""}`).join("\n")}`,
4254
+ { details: { readiness: productionReadiness } }
4255
+ );
4256
+ }
4014
4257
  const stagingGateResult = resumeAtRootGates ? completedJournalStepData(root, workflowRun.runId, "release-staging-gates") : await executeJournalStep(root, workflowRun.runId, "release-staging-gates", () => {
4015
4258
  helpers.write("[release][workflow] Verifying current staging gates before production release.");
4016
4259
  const packageGates = checkedOutWorkspacePackageRepos(root).filter((pkg) => effectiveSelectedPackageNames.has(pkg.name)).map((pkg) => ({
@@ -4119,9 +4362,12 @@ async function workflowRelease(helpers, input) {
4119
4362
  }).then((workflowGates) => ({ workflowGates })));
4120
4363
  const hostedDeploymentState2 = recordHostedDeploymentStatesFromRootGates(root, rootRelease2, rootWorkflowGateResult2?.workflowGates);
4121
4364
  ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
4122
- const hostingAudit2 = await runReadOnlyHostingAuditForWorkflow("release", root, helpers, "prod", {
4365
+ const applicationSelection2 = selectWorkflowApplications(root, { changedPaths: ["treeseed.site.yaml"] });
4366
+ const hostingAudit2 = await runWorkflowHostedResourceVerification("release", root, helpers, "prod", {
4123
4367
  enabled: true,
4124
- strict: false
4368
+ strict: effectiveInput.verifyDeployedResources === true,
4369
+ live: effectiveInput.verifyDeployedResources === true,
4370
+ appId: singleSelectedWorkflowAppId(applicationSelection2)
4125
4371
  });
4126
4372
  const releaseBackMerge2 = await executeJournalStep(root, workflowRun.runId, "release-back-merge", () => backMergeRootProductionIntoStaging(root, false, {
4127
4373
  version: rootVersion,
@@ -4144,6 +4390,7 @@ async function workflowRelease(helpers, input) {
4144
4390
  productionBranch: PRODUCTION_BRANCH,
4145
4391
  touchedPackages: [],
4146
4392
  packageSelection: { changed: [], dependents: [], selected: [] },
4393
+ applicationSelection: applicationSelection2,
4147
4394
  publishWait: [],
4148
4395
  repos: [],
4149
4396
  rootRepo,
@@ -4249,7 +4496,8 @@ async function workflowRelease(helpers, input) {
4249
4496
  changelog,
4250
4497
  extraLines: [`Package: ${pkg.name}`]
4251
4498
  }),
4252
- pushTarget: true
4499
+ pushTarget: true,
4500
+ allowUnrelatedHistories: true
4253
4501
  });
4254
4502
  const tag = ensureReleaseTag(pkg.dir, tagName, mergeResult.commitSha, releaseAdminMessage({
4255
4503
  subject: `release: ${tagName}`,
@@ -4430,9 +4678,12 @@ async function workflowRelease(helpers, input) {
4430
4678
  }).then((workflowGates) => ({ workflowGates })));
4431
4679
  const hostedDeploymentState = recordHostedDeploymentStatesFromRootGates(root, rootRelease, rootWorkflowGateResult?.workflowGates);
4432
4680
  ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
4433
- const hostingAudit = await runReadOnlyHostingAuditForWorkflow("release", root, helpers, "prod", {
4681
+ const applicationSelection = releasePlan.applicationSelection;
4682
+ const hostingAudit = await runWorkflowHostedResourceVerification("release", root, helpers, "prod", {
4434
4683
  enabled: true,
4435
- strict: false
4684
+ strict: effectiveInput.verifyDeployedResources === true,
4685
+ live: effectiveInput.verifyDeployedResources === true,
4686
+ appId: singleSelectedWorkflowAppId(applicationSelection)
4436
4687
  });
4437
4688
  const releaseBackMerge = await executeJournalStep(root, workflowRun.runId, "release-back-merge", () => backMergeRootProductionIntoStaging(root, true, {
4438
4689
  version: rootVersion,
@@ -4465,6 +4716,7 @@ async function workflowRelease(helpers, input) {
4465
4716
  productionBranch: PRODUCTION_BRANCH,
4466
4717
  touchedPackages: effectivePackageSelection.selected,
4467
4718
  packageSelection: effectivePackageSelection,
4719
+ applicationSelection,
4468
4720
  replacedDevReferences,
4469
4721
  releaseInstalls,
4470
4722
  devTagCleanup,
@@ -4720,6 +4972,7 @@ async function workflowDestroy(helpers, input) {
4720
4972
  const dryRun = executionMode === "plan";
4721
4973
  const force = input.force === true;
4722
4974
  const deleteData = input.deleteData === true;
4975
+ const sweepTreeseed = input.sweepTreeseed === true;
4723
4976
  const destroyRemote = input.destroyRemote !== false;
4724
4977
  const destroyLocal = input.destroyLocal !== false;
4725
4978
  const removeBuildArtifacts = input.removeBuildArtifacts === true;
@@ -4733,6 +4986,7 @@ async function workflowDestroy(helpers, input) {
4733
4986
  dryRun,
4734
4987
  force,
4735
4988
  deleteData,
4989
+ sweepTreeseed,
4736
4990
  destroyRemote,
4737
4991
  destroyLocal,
4738
4992
  removeBuildArtifacts,
@@ -4743,12 +4997,13 @@ async function workflowDestroy(helpers, input) {
4743
4997
  },
4744
4998
  plannedSteps: [
4745
4999
  ...destroyRemote ? [{ id: "destroy-remote", description: `Destroy remote ${scope} resources` }] : [],
5000
+ ...sweepTreeseed ? [{ id: "sweep-treeseed-resources", description: "Sweep TreeSeed-owned provider resources across persistent environments" }] : [],
4746
5001
  ...destroyLocal ? [{ id: "cleanup-local", description: `Clean local ${scope} state${removeBuildArtifacts ? " and build artifacts" : ""}` }] : []
4747
5002
  ],
4748
5003
  remoteResult: null
4749
5004
  };
4750
5005
  if (executionMode === "plan") {
4751
- const plannedRemoteResult = destroyRemote ? await destroyTreeseedEnvironmentResources(tenantRoot, { dryRun: true, force, deleteData, target }) : null;
5006
+ const plannedRemoteResult = destroyRemote ? await destroyTreeseedEnvironmentResources(tenantRoot, { dryRun: true, force, deleteData, sweepTreeseed, target }) : null;
4752
5007
  return buildWorkflowResult(
4753
5008
  "destroy",
4754
5009
  tenantRoot,
@@ -4759,7 +5014,7 @@ async function workflowDestroy(helpers, input) {
4759
5014
  {
4760
5015
  executionMode,
4761
5016
  nextSteps: createNextSteps([
4762
- { operation: "destroy", reason: "Run without --plan to destroy the selected environment.", input: { environment: scope, force, deleteData, removeBuildArtifacts } },
5017
+ { operation: "destroy", reason: "Run without --plan to destroy the selected environment.", input: { environment: scope, force, deleteData, sweepTreeseed, removeBuildArtifacts } },
4763
5018
  { operation: "status", reason: "Confirm the current environment state before making destructive changes." }
4764
5019
  ])
4765
5020
  }
@@ -4772,6 +5027,7 @@ async function workflowDestroy(helpers, input) {
4772
5027
  environment: scope,
4773
5028
  force,
4774
5029
  deleteData,
5030
+ sweepTreeseed,
4775
5031
  destroyRemote,
4776
5032
  destroyLocal,
4777
5033
  removeBuildArtifacts
@@ -4801,7 +5057,7 @@ async function workflowDestroy(helpers, input) {
4801
5057
  if (!confirmed) {
4802
5058
  workflowError("destroy", "confirmation_required", `Destroy confirmation required. Re-run with confirm="${expectedConfirmation}".`);
4803
5059
  }
4804
- const remoteResult = destroyRemote ? await executeJournalStep(root, workflowRun.runId, "destroy-remote", () => destroyTreeseedEnvironmentResources(tenantRoot, { dryRun: false, force, deleteData, target })) : null;
5060
+ const remoteResult = destroyRemote ? await executeJournalStep(root, workflowRun.runId, "destroy-remote", () => destroyTreeseedEnvironmentResources(tenantRoot, { dryRun: false, force, deleteData, sweepTreeseed, target })) : null;
4805
5061
  if (!destroyRemote) {
4806
5062
  skipJournalStep(root, workflowRun.runId, "destroy-remote", { skippedReason: "destroyRemote=false" });
4807
5063
  }