@treeseed/sdk 0.6.39 → 0.6.41

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 (44) hide show
  1. package/dist/capacity.d.ts +53 -0
  2. package/dist/capacity.js +100 -0
  3. package/dist/control-plane-client.d.ts +41 -1
  4. package/dist/control-plane-client.js +154 -0
  5. package/dist/control-plane.d.ts +6 -1
  6. package/dist/control-plane.js +39 -2
  7. package/dist/d1-store.d.ts +63 -1
  8. package/dist/d1-store.js +190 -1
  9. package/dist/index.d.ts +3 -1
  10. package/dist/index.js +12 -0
  11. package/dist/operations/services/config-runtime.js +2 -2
  12. package/dist/operations/services/deploy.js +3 -2
  13. package/dist/operations/services/knowledge-coop-launch.js +5 -28
  14. package/dist/operations/services/package-reference-policy.d.ts +68 -0
  15. package/dist/operations/services/package-reference-policy.js +135 -0
  16. package/dist/operations/services/project-platform.d.ts +14 -0
  17. package/dist/operations/services/project-platform.js +3 -2
  18. package/dist/operations/services/railway-api.d.ts +33 -0
  19. package/dist/operations/services/railway-api.js +273 -0
  20. package/dist/operations/services/railway-deploy.d.ts +22 -0
  21. package/dist/operations/services/railway-deploy.js +216 -18
  22. package/dist/operations/services/release-candidate.d.ts +2 -0
  23. package/dist/operations/services/release-candidate.js +28 -0
  24. package/dist/operations/services/runtime-tools.js +1 -1
  25. package/dist/operations-registry.js +1 -0
  26. package/dist/reconcile/bootstrap-systems.js +1 -1
  27. package/dist/reconcile/builtin-adapters.js +5 -9
  28. package/dist/reconcile/contracts.d.ts +1 -1
  29. package/dist/reconcile/desired-state.js +9 -17
  30. package/dist/reconcile/state.js +4 -4
  31. package/dist/reconcile/units.js +4 -8
  32. package/dist/sdk-types.d.ts +566 -3
  33. package/dist/sdk.d.ts +12 -1
  34. package/dist/sdk.js +44 -0
  35. package/dist/stores/operational-store.d.ts +12 -1
  36. package/dist/stores/operational-store.js +283 -5
  37. package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +5 -24
  38. package/dist/types/agents.d.ts +27 -0
  39. package/dist/workflow/operations.d.ts +94 -2
  40. package/dist/workflow/operations.js +90 -32
  41. package/dist/workflow-state.js +3 -5
  42. package/dist/workflow.d.ts +8 -1
  43. package/dist/workflow.js +6 -0
  44. package/package.json +5 -1
@@ -10,6 +10,7 @@ import {
10
10
  ensureRailwayProject,
11
11
  ensureRailwayService,
12
12
  ensureRailwayServiceInstanceConfiguration,
13
+ ensureRailwayServiceVolume,
13
14
  listRailwayEnvironments,
14
15
  listRailwayProjects,
15
16
  listRailwayServices,
@@ -18,7 +19,8 @@ import {
18
19
  resolveRailwayApiToken,
19
20
  resolveRailwayApiUrl,
20
21
  resolveRailwayWorkspace,
21
- resolveRailwayWorkspaceContext
22
+ resolveRailwayWorkspaceContext,
23
+ upsertRailwayVariables
22
24
  } from "./railway-api.js";
23
25
  function normalizeScope(scope) {
24
26
  return scope === "prod" ? "prod" : scope === "staging" ? "staging" : "local";
@@ -26,13 +28,29 @@ function normalizeScope(scope) {
26
28
  function resolveRailwayEnvironmentForScope(scope, configuredEnvironment) {
27
29
  return normalizeRailwayEnvironmentName(configuredEnvironment || normalizeScope(scope));
28
30
  }
29
- const RAILWAY_SERVICE_KEYS = ["api", "manager", "worker", "workdayStart", "workdayReport"];
30
- const HOSTED_PROJECT_SERVICE_KEYS = ["api", "manager", "worker"];
31
+ const RAILWAY_SERVICE_KEYS = ["api", "workdayManager", "workerRunner"];
32
+ const HOSTED_PROJECT_SERVICE_KEYS = ["api", "workdayManager", "workerRunner"];
33
+ const WORKER_RUNNER_BOOTSTRAP_INDEX = 1;
34
+ const WORKER_RUNNER_VOLUME_MOUNT_PATH = "/data";
31
35
  function shouldManageRailwaySchedules(scope, phase = "deploy") {
32
- return phase === "deploy" && normalizeRailwayEnvironmentName(scope) === "production";
36
+ const environment = normalizeRailwayEnvironmentName(scope);
37
+ return phase === "deploy" && (environment === "staging" || environment === "production");
33
38
  }
34
39
  function railwayServiceNameSuffix(serviceKey) {
35
- return serviceKey === "workdayStart" ? "workday-start" : serviceKey === "workdayReport" ? "workday-report" : serviceKey;
40
+ return serviceKey === "workdayManager" ? "workday-manager" : serviceKey === "workerRunner" ? "worker-runner" : serviceKey;
41
+ }
42
+ function deriveRailwayWorkerRunnerServiceName(projectSlug, index = WORKER_RUNNER_BOOTSTRAP_INDEX) {
43
+ const normalizedIndex = Math.max(1, Number.parseInt(String(index), 10) || WORKER_RUNNER_BOOTSTRAP_INDEX);
44
+ return `${projectSlug}-worker-runner-${String(normalizedIndex).padStart(2, "0")}`;
45
+ }
46
+ function deriveRailwayWorkerRunnerVolumeName(serviceName) {
47
+ return `${serviceName}-data`;
48
+ }
49
+ function railwayServiceRuntimeStartCommand(service) {
50
+ if (service.key === "workdayManager" && Array.isArray(service.schedule) && service.schedule.length > 0) {
51
+ return `node -e "console.log('workday-manager scheduled-only service idle')"`;
52
+ }
53
+ return service.startCommand;
36
54
  }
37
55
  function normalizeScheduleExpressions(value) {
38
56
  if (typeof value === "string" && value.trim()) {
@@ -55,6 +73,61 @@ function configuredEnvValue(env, name) {
55
73
  const value = env?.[name];
56
74
  return typeof value === "string" && value.trim() ? value.trim() : "";
57
75
  }
76
+ function parseRailwayJsonOutput(output) {
77
+ const trimmed = typeof output === "string" ? output.trim() : "";
78
+ if (!trimmed) {
79
+ return null;
80
+ }
81
+ try {
82
+ return JSON.parse(trimmed);
83
+ } catch {
84
+ }
85
+ const lines = trimmed.split(/\r?\n/u);
86
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
87
+ const candidate = lines.slice(index).join("\n").trim();
88
+ if (!candidate.startsWith("{") && !candidate.startsWith("[")) {
89
+ continue;
90
+ }
91
+ try {
92
+ return JSON.parse(candidate);
93
+ } catch {
94
+ }
95
+ }
96
+ return null;
97
+ }
98
+ function normalizeRailwayCliVolume(value, { serviceId, environmentId, fallbackName, fallbackMountPath }) {
99
+ if (!value || typeof value !== "object") {
100
+ return null;
101
+ }
102
+ const record = value;
103
+ const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : "";
104
+ if (!id) {
105
+ return null;
106
+ }
107
+ const name = typeof record.name === "string" && record.name.trim() ? record.name.trim() : fallbackName;
108
+ const mountPath = typeof record.mountPath === "string" && record.mountPath.trim() ? record.mountPath.trim() : fallbackMountPath;
109
+ const sizeMb = typeof record.sizeMB === "number" ? record.sizeMB : null;
110
+ const currentSizeMb = typeof record.currentSizeMB === "number" ? record.currentSizeMB : null;
111
+ return {
112
+ id,
113
+ name,
114
+ projectId: null,
115
+ instances: [{
116
+ id,
117
+ serviceId,
118
+ environmentId,
119
+ mountPath,
120
+ sizeGb: sizeMb === null ? null : sizeMb / 1e3,
121
+ usedGb: currentSizeMb === null ? null : currentSizeMb / 1e3
122
+ }]
123
+ };
124
+ }
125
+ function normalizeRailwayCliVolumeList(value, options) {
126
+ if (!value || typeof value !== "object" || !Array.isArray(value.volumes)) {
127
+ return [];
128
+ }
129
+ return value.volumes.map((entry) => normalizeRailwayCliVolume(entry, options)).filter(Boolean);
130
+ }
58
131
  function isUsableRailwayToken(value) {
59
132
  return typeof value === "string" && value.trim().length >= 8;
60
133
  }
@@ -455,7 +528,7 @@ function configuredRailwayServices(tenantRoot, scope) {
455
528
  if (!service || service.enabled === false || (service.provider ?? "railway") !== "railway") {
456
529
  return null;
457
530
  }
458
- const defaultRootDir = ["api", "manager", "worker", "workdayStart", "workdayReport"].includes(serviceKey) ? "." : "packages/core";
531
+ const defaultRootDir = ["api", "workdayManager", "workerRunner"].includes(serviceKey) ? "." : "packages/core";
459
532
  const serviceRoot = resolve(tenantRoot, service.railway?.rootDir ?? service.rootDir ?? defaultRootDir);
460
533
  const railwayEnvironment = resolveRailwayEnvironmentForScope(
461
534
  normalizedScope,
@@ -468,7 +541,7 @@ function configuredRailwayServices(tenantRoot, scope) {
468
541
  projectId: service.railway?.projectId ?? null,
469
542
  projectName: service.railway?.projectName ?? identity.deploymentKey,
470
543
  serviceId: service.railway?.serviceId ?? null,
471
- serviceName: service.railway?.serviceName ?? `${identity.deploymentKey}-${railwayServiceNameSuffix(serviceKey)}`,
544
+ serviceName: service.railway?.serviceName ?? (serviceKey === "workerRunner" ? deriveRailwayWorkerRunnerServiceName(identity.deploymentKey) : `${identity.deploymentKey}-${railwayServiceNameSuffix(serviceKey)}`),
472
545
  rootDir: serviceRoot,
473
546
  publicBaseUrl,
474
547
  railwayEnvironment,
@@ -480,7 +553,11 @@ function configuredRailwayServices(tenantRoot, scope) {
480
553
  restartPolicy: service.railway?.restartPolicy ?? null,
481
554
  runtimeMode: service.railway?.runtimeMode ?? null,
482
555
  schedule: normalizeScheduleExpressions(service.railway?.schedule),
483
- hostingKind
556
+ hostingKind,
557
+ runnerPool: serviceKey === "workerRunner" ? {
558
+ bootstrapIndex: WORKER_RUNNER_BOOTSTRAP_INDEX,
559
+ volumeMountPath: WORKER_RUNNER_VOLUME_MOUNT_PATH
560
+ } : null
484
561
  };
485
562
  }).filter(Boolean);
486
563
  }
@@ -879,7 +956,8 @@ async function resolveRailwayDeployProjectContext(service, { env = process.env }
879
956
  }
880
957
  async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, service, { env = process.env } = {}) {
881
958
  const wantsInstanceConfig = service.buildCommand || service.startCommand || service.rootDir || service.healthcheckPath || service.healthcheckTimeoutSeconds !== null || service.healthcheckTimeoutSeconds !== void 0 || service.healthcheckIntervalSeconds !== null || service.healthcheckIntervalSeconds !== void 0 || service.restartPolicy || service.runtimeMode;
882
- if (!wantsInstanceConfig) {
959
+ const wantsRunnerVolume = service.key === "workerRunner";
960
+ if (!wantsInstanceConfig && !wantsRunnerVolume) {
883
961
  return null;
884
962
  }
885
963
  const workspace = await resolveRailwayWorkspaceContext({ env });
@@ -922,11 +1000,11 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
922
1000
  env
923
1001
  }).then((result) => result.service);
924
1002
  }
925
- return await ensureRailwayServiceInstanceConfiguration({
1003
+ const runtimeConfiguration = wantsInstanceConfig ? await ensureRailwayServiceInstanceConfiguration({
926
1004
  serviceId: railwayService.id,
927
1005
  environmentId: environment.id,
928
1006
  buildCommand: service.buildCommand,
929
- startCommand: service.startCommand,
1007
+ startCommand: railwayServiceRuntimeStartCommand(service),
930
1008
  rootDirectory: relativeRailwayRootDir(tenantRoot, service.rootDir),
931
1009
  healthcheckPath: service.healthcheckPath,
932
1010
  healthcheckTimeoutSeconds: service.healthcheckTimeoutSeconds,
@@ -934,7 +1012,123 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
934
1012
  restartPolicy: service.restartPolicy,
935
1013
  runtimeMode: service.runtimeMode,
936
1014
  env
1015
+ }) : null;
1016
+ const volumeMountPath = service.runnerPool?.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH;
1017
+ const volumeConfiguration = wantsRunnerVolume ? await ensureRailwayServiceVolumeWithCliFallback({
1018
+ tenantRoot,
1019
+ projectId: project.id,
1020
+ environmentId: environment.id,
1021
+ environmentName: environment.name,
1022
+ serviceId: railwayService.id,
1023
+ serviceName: railwayService.name,
1024
+ name: deriveRailwayWorkerRunnerVolumeName(railwayService.name),
1025
+ mountPath: volumeMountPath,
1026
+ env
1027
+ }) : null;
1028
+ if (wantsRunnerVolume) {
1029
+ await upsertRailwayVariables({
1030
+ projectId: project.id,
1031
+ environmentId: environment.id,
1032
+ serviceId: railwayService.id,
1033
+ variables: {
1034
+ TREESEED_RUNNER_SERVICE_NAME: railwayService.name,
1035
+ TREESEED_RUNNER_VOLUME_ROOT: volumeMountPath,
1036
+ TREESEED_RUNNER_VOLUME_NAME: volumeConfiguration?.volume.name ?? deriveRailwayWorkerRunnerVolumeName(railwayService.name),
1037
+ TREESEED_WORKER_IDLE_EXIT_MS: configuredEnvValue(env, "TREESEED_WORKER_IDLE_EXIT_MS") || "60000",
1038
+ ...volumeConfiguration?.volume.id ? { TREESEED_RUNNER_VOLUME_ID: volumeConfiguration.volume.id } : {}
1039
+ },
1040
+ env
1041
+ });
1042
+ }
1043
+ return {
1044
+ instance: runtimeConfiguration?.instance ?? null,
1045
+ updated: Boolean(runtimeConfiguration?.updated || volumeConfiguration?.updated || volumeConfiguration?.created),
1046
+ volume: volumeConfiguration ? {
1047
+ id: volumeConfiguration.volume.id,
1048
+ name: volumeConfiguration.volume.name,
1049
+ mountPath: volumeConfiguration.instance?.mountPath ?? volumeMountPath,
1050
+ created: volumeConfiguration.created,
1051
+ updated: volumeConfiguration.updated
1052
+ } : null
1053
+ };
1054
+ }
1055
+ async function ensureRailwayServiceVolumeWithCliFallback({
1056
+ tenantRoot,
1057
+ projectId,
1058
+ environmentId,
1059
+ environmentName,
1060
+ serviceId,
1061
+ serviceName,
1062
+ name,
1063
+ mountPath,
1064
+ env = process.env
1065
+ }) {
1066
+ try {
1067
+ return await ensureRailwayServiceVolume({
1068
+ projectId,
1069
+ environmentId,
1070
+ serviceId,
1071
+ name,
1072
+ mountPath,
1073
+ env
1074
+ });
1075
+ } catch (error) {
1076
+ const message = error instanceof Error ? error.message : String(error);
1077
+ if (!message.includes("Problem processing request")) {
1078
+ throw error;
1079
+ }
1080
+ }
1081
+ const cliOptions = {
1082
+ cwd: tenantRoot,
1083
+ capture: true,
1084
+ env
1085
+ };
1086
+ const volumeArgs = ["volume", "--service", serviceId, "--environment", environmentId];
1087
+ const listResult = runRailway([...volumeArgs, "list", "--json"], cliOptions);
1088
+ const existingVolumes = normalizeRailwayCliVolumeList(parseRailwayJsonOutput(listResult.stdout ?? ""), {
1089
+ serviceId,
1090
+ environmentId,
1091
+ fallbackName: name,
1092
+ fallbackMountPath: mountPath
937
1093
  });
1094
+ let volume = existingVolumes.find((entry) => entry.name === name) ?? existingVolumes.find((entry) => entry.instances.some((instance2) => instance2.mountPath === mountPath)) ?? existingVolumes[0] ?? null;
1095
+ let created = false;
1096
+ let updated = false;
1097
+ if (!volume) {
1098
+ const createResult = runRailway([...volumeArgs, "add", "--mount-path", mountPath, "--json"], cliOptions);
1099
+ volume = normalizeRailwayCliVolume(parseRailwayJsonOutput(createResult.stdout ?? ""), {
1100
+ serviceId,
1101
+ environmentId,
1102
+ fallbackName: name,
1103
+ fallbackMountPath: mountPath
1104
+ });
1105
+ if (!volume) {
1106
+ throw new Error(`Railway CLI volume add did not return a usable volume for ${serviceName} in ${environmentName}.`);
1107
+ }
1108
+ created = true;
1109
+ }
1110
+ const instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null;
1111
+ if (volume.name !== name || instance?.mountPath !== mountPath) {
1112
+ const updateResult = runRailway([...volumeArgs, "update", "--volume", volume.id, "--name", name, "--mount-path", mountPath, "--json"], cliOptions);
1113
+ const updatedVolume = normalizeRailwayCliVolume(parseRailwayJsonOutput(updateResult.stdout ?? ""), {
1114
+ serviceId,
1115
+ environmentId,
1116
+ fallbackName: name,
1117
+ fallbackMountPath: mountPath
1118
+ });
1119
+ volume = updatedVolume ?? {
1120
+ ...volume,
1121
+ name,
1122
+ instances: volume.instances.map((entry) => ({ ...entry, mountPath }))
1123
+ };
1124
+ updated = true;
1125
+ }
1126
+ return {
1127
+ volume,
1128
+ instance: volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null,
1129
+ created,
1130
+ updated
1131
+ };
938
1132
  }
939
1133
  async function deployRailwayService(tenantRoot, service, {
940
1134
  dryRun = false,
@@ -954,13 +1148,16 @@ async function deployRailwayService(tenantRoot, service, {
954
1148
  }
955
1149
  const deployService = await resolveRailwayDeployProjectContext(service, { env });
956
1150
  const plan = planRailwayServiceDeploy(deployService, { env });
1151
+ const commandEnv = buildRailwayCommandEnv({ ...process.env, ...env });
957
1152
  const taskPrefix = prefix ?? {
958
1153
  scope: normalizeScope(deployService.scope ?? deployService.railwayEnvironment ?? "railway"),
959
1154
  system: deployService.key === "api" ? "api" : "agents",
960
1155
  task: `${deployService.key}-railway-deploy`,
961
1156
  stage: "deploy"
962
1157
  };
963
- const commandEnv = buildRailwayCommandEnv({ ...process.env, ...env });
1158
+ const runtimeConfiguration = await syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, deployService, {
1159
+ env: commandEnv
1160
+ });
964
1161
  if (deployService.buildCommand) {
965
1162
  const buildResult = await runPrefixedCommand("bash", ["-lc", deployService.buildCommand], {
966
1163
  cwd: deployService.rootDir,
@@ -996,9 +1193,6 @@ async function deployRailwayService(tenantRoot, service, {
996
1193
  if (lastFailure) {
997
1194
  throw new Error(lastFailure.stderr?.trim() || lastFailure.stdout?.trim() || `railway ${plan.args.join(" ")} failed`);
998
1195
  }
999
- const runtimeConfiguration = await syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, deployService, {
1000
- env: commandEnv
1001
- });
1002
1196
  return {
1003
1197
  service: deployService.key,
1004
1198
  status: "deployed",
@@ -1007,9 +1201,10 @@ async function deployRailwayService(tenantRoot, service, {
1007
1201
  publicBaseUrl: deployService.publicBaseUrl,
1008
1202
  runtimeConfiguration: runtimeConfiguration ? {
1009
1203
  updated: runtimeConfiguration.updated,
1010
- healthcheckPath: runtimeConfiguration.instance.healthcheckPath,
1011
- healthcheckTimeoutSeconds: runtimeConfiguration.instance.healthcheckTimeoutSeconds,
1012
- runtimeMode: runtimeConfiguration.instance.runtimeMode
1204
+ healthcheckPath: runtimeConfiguration.instance?.healthcheckPath ?? null,
1205
+ healthcheckTimeoutSeconds: runtimeConfiguration.instance?.healthcheckTimeoutSeconds ?? null,
1206
+ runtimeMode: runtimeConfiguration.instance?.runtimeMode ?? null,
1207
+ volume: runtimeConfiguration.volume ?? null
1013
1208
  } : null
1014
1209
  };
1015
1210
  }
@@ -1018,6 +1213,8 @@ export {
1018
1213
  configuredRailwayScheduledJobs,
1019
1214
  configuredRailwayServices,
1020
1215
  deployRailwayService,
1216
+ deriveRailwayWorkerRunnerServiceName,
1217
+ deriveRailwayWorkerRunnerVolumeName,
1021
1218
  ensureRailwayEnvironmentExists,
1022
1219
  ensureRailwayProjectContext,
1023
1220
  ensureRailwayProjectExists,
@@ -1026,6 +1223,7 @@ export {
1026
1223
  isRailwayTransientFailure,
1027
1224
  isUsableRailwayToken,
1028
1225
  planRailwayServiceDeploy,
1226
+ railwayServiceRuntimeStartCommand,
1029
1227
  resolveRailwayAuthToken,
1030
1228
  resolveRailwayDeploymentProfile,
1031
1229
  runRailway,
@@ -8,6 +8,7 @@ export type ReleaseCandidateFailure = {
8
8
  };
9
9
  export type ReleaseCandidateFingerprint = {
10
10
  key: string;
11
+ policyVersion: string;
11
12
  rootSha: string | null;
12
13
  packageShas: Record<string, string | null>;
13
14
  plannedVersions: Record<string, string>;
@@ -36,4 +37,5 @@ export type ReleaseCandidateInput = {
36
37
  export declare function buildReleaseCandidateFingerprint(input: ReleaseCandidateInput): ReleaseCandidateFingerprint;
37
38
  export declare function readCachedReleaseCandidateReport(root: string, key: string): ReleaseCandidateReport | null;
38
39
  export declare function writeReleaseCandidateReport(root: string, report: ReleaseCandidateReport): ReleaseCandidateReport;
40
+ export declare function collectReleaseCandidateOutputFailures(line: string): string[];
39
41
  export declare function runReleaseCandidateGate(input: ReleaseCandidateInput): Promise<ReleaseCandidateReport>;
@@ -13,6 +13,7 @@ import { loadCliDeployConfig } from "./runtime-tools.js";
13
13
  import { packagesWithScript, run, workspacePackages } from "./workspace-tools.js";
14
14
  import { createBuildWarningSummary, formatAllowedBuildWarnings } from "./build-warning-policy.js";
15
15
  const RELEASE_CANDIDATE_CACHE_DIR = ".treeseed/workflow/release-candidates";
16
+ const RELEASE_CANDIDATE_POLICY_VERSION = "strict-output-v1";
16
17
  const STABLE_SEMVER = /^\d+\.\d+\.\d+$/u;
17
18
  const REHEARSAL_IGNORED_SEGMENTS = /* @__PURE__ */ new Set([
18
19
  ".git",
@@ -86,6 +87,7 @@ function buildReleaseCandidateFingerprint(input) {
86
87
  ...Object.fromEntries(packages.map((pkg) => [pkg.name, fileSha256(resolve(pkg.dir, "package-lock.json"))]))
87
88
  });
88
89
  const base = {
90
+ policyVersion: RELEASE_CANDIDATE_POLICY_VERSION,
89
91
  rootSha: safeGitHead(input.root),
90
92
  packageShas,
91
93
  plannedVersions,
@@ -241,11 +243,15 @@ function runNpmRehearsalCommand(args, options) {
241
243
  throw new Error(message);
242
244
  }
243
245
  const warningSummary = createBuildWarningSummary();
246
+ const outputFailures = [];
244
247
  const emitFiltered = (text, stream) => {
245
248
  for (const line of text.split(/\r?\n/u)) {
246
249
  if (!line) continue;
247
250
  const classified = warningSummary.record(line);
248
251
  if (classified.kind === "allowed") continue;
252
+ for (const failure of collectReleaseCandidateOutputFailures(line)) {
253
+ outputFailures.push(failure);
254
+ }
249
255
  stream.write(`${line}
250
256
  `);
251
257
  }
@@ -256,6 +262,27 @@ function runNpmRehearsalCommand(args, options) {
256
262
  process.stdout.write(`${line}
257
263
  `);
258
264
  }
265
+ if (outputFailures.length > 0) {
266
+ throw new Error([
267
+ `npm ${args.join(" ")} completed with error output despite exit code 0.`,
268
+ ...outputFailures.slice(0, 12)
269
+ ].join("\n"));
270
+ }
271
+ }
272
+ function collectReleaseCandidateOutputFailures(line) {
273
+ const value = String(line ?? "").trim();
274
+ if (!value) return [];
275
+ const failures = [];
276
+ if (/^stderr\s+\|\s+/u.test(value)) {
277
+ failures.push(`Captured test stderr: ${value}`);
278
+ }
279
+ if (/(^|\s)ERROR(?:\s|\[|:)/u.test(value)) {
280
+ failures.push(`Error output: ${value}`);
281
+ }
282
+ if (/\bFailed to run background task\b/u.test(value)) {
283
+ failures.push(`Background task failure output: ${value}`);
284
+ }
285
+ return failures;
259
286
  }
260
287
  function buildRehearsalWorkspacePackageArtifacts(root) {
261
288
  for (const pkg of packagesWithScript("build:dist", root)) {
@@ -561,6 +588,7 @@ async function runReleaseCandidateGate(input) {
561
588
  }
562
589
  export {
563
590
  buildReleaseCandidateFingerprint,
591
+ collectReleaseCandidateOutputFailures,
564
592
  readCachedReleaseCandidateReport,
565
593
  runReleaseCandidateGate,
566
594
  writeReleaseCandidateReport
@@ -53,7 +53,7 @@ const TREESEED_DEFAULT_PROVIDER_SELECTIONS = {
53
53
  },
54
54
  site: "default"
55
55
  };
56
- const TRESEED_MANAGED_SERVICE_KEYS = ["api", "manager", "worker", "workdayStart", "workdayReport"];
56
+ const TRESEED_MANAGED_SERVICE_KEYS = ["api", "workdayManager", "workerRunner"];
57
57
  const TRESEED_WORKSPACE_PACKAGE_DIRS = ["sdk", "core", "cli"];
58
58
  const CLOUDFLARE_ACCOUNT_ID_PLACEHOLDER = "replace-with-cloudflare-account-id";
59
59
  function normalizePlanesFromLegacyHosting(hosting) {
@@ -10,6 +10,7 @@ const TRESEED_OPERATION_SPECS = [
10
10
  operation({ id: "branch.save", name: "save", aliases: [], group: "Workflow", summary: "Recursively verify, commit, and push the current task checkpoint.", description: "Save dirty package repos in dependency order, then verify, commit, push, and optionally refresh preview for market.", provider: "default", related: ["switch", "stage", "status"] }),
11
11
  operation({ id: "branch.close", name: "close", aliases: [], group: "Workflow", summary: "Recursively archive and delete a task branch.", description: "Auto-save if needed, clean preview resources, create deprecated tags, and remove the task branch across market and checked-out package repos.", provider: "default", related: ["tasks", "switch", "stage"] }),
12
12
  operation({ id: "branch.stage", name: "stage", aliases: [], group: "Workflow", summary: "Squash a task branch into staging across market and packages.", description: "Auto-save if needed, squash-merge package task branches into staging first, update market submodule pointers, then squash-merge market into staging and clean up the task branch.", provider: "default", related: ["save", "release", "close"] }),
13
+ operation({ id: "workspace.tags.cleanup", name: "tags:cleanup", aliases: [], group: "Utilities", summary: "Clean stale Treeseed-managed package dev tags.", description: "Plan or delete stale staging and preview package dev tags that are older than each package current version line while preserving active references and non-Treeseed tags.", provider: "default", related: ["release", "status"] }),
13
14
  operation({ id: "workspace.resume", name: "resume", aliases: [], group: "Workflow", summary: "Resume an interrupted workflow run.", description: "Continue a failed journaled workflow run from its next incomplete step after validating current workspace preconditions.", provider: "default", related: ["recover", "status"] }),
14
15
  operation({ id: "workspace.recover", name: "recover", aliases: [], group: "Workflow", summary: "Inspect active workflow locks and interrupted runs.", description: "List active workflow locks, resumable interrupted runs, and the exact commands needed to continue or recover the workspace state.", provider: "default", related: ["resume", "status"] }),
15
16
  operation({ id: "workspace.links.status", name: workspaceCommand("status"), aliases: [], group: "Utilities", summary: "Inspect local workspace dependency links.", description: "Inspect whether Treeseed package dependencies are linked to local package checkouts or resolved through deployment references.", provider: "default", related: ["dev", "save"] }),
@@ -55,7 +55,7 @@ function agentsSystemDisabled(config) {
55
55
  if (config.runtime?.mode === "none") {
56
56
  return "runtime.mode is none.";
57
57
  }
58
- const enabled = ["manager", "worker", "workdayStart", "workdayReport"].some((serviceKey) => serviceEnabled(config, serviceKey));
58
+ const enabled = ["workdayManager", "workerRunner"].some((serviceKey) => serviceEnabled(config, serviceKey));
59
59
  return enabled ? null : "No agent Railway services are enabled.";
60
60
  }
61
61
  function hasValue(env, key) {
@@ -1439,7 +1439,7 @@ async function syncRailwayEnvironmentForScope(input, { dryRun = false } = {}) {
1439
1439
  includeInstances: !dryRun,
1440
1440
  includeVariables: false
1441
1441
  });
1442
- const workerEntry = topology.services.get("worker") ?? null;
1442
+ const workerEntry = topology.services.get("workerRunner") ?? topology.services.get("worker") ?? null;
1443
1443
  const railwayRuntimeVariables = Object.fromEntries(
1444
1444
  [
1445
1445
  ["TREESEED_RAILWAY_PROJECT_ID", workerEntry?.project?.id],
@@ -2140,16 +2140,12 @@ function createCloudflareReconcileAdapters() {
2140
2140
  function createRailwayReconcileAdapters() {
2141
2141
  return [
2142
2142
  buildRailwayAdapter("railway-service:api"),
2143
- buildRailwayAdapter("railway-service:manager"),
2144
- buildRailwayAdapter("railway-service:worker"),
2145
- buildRailwayAdapter("railway-service:workday-start"),
2146
- buildRailwayAdapter("railway-service:workday-report"),
2143
+ buildRailwayAdapter("railway-service:workday-manager"),
2144
+ buildRailwayAdapter("railway-service:worker-runner"),
2147
2145
  buildCustomDomainAdapter("custom-domain:api", "railway"),
2148
2146
  buildCompositeAdapter("api-runtime"),
2149
- buildCompositeAdapter("manager-runtime"),
2150
- buildCompositeAdapter("worker-runtime"),
2151
- buildCompositeAdapter("workday-start-runtime"),
2152
- buildCompositeAdapter("workday-report-runtime")
2147
+ buildCompositeAdapter("workday-manager-runtime"),
2148
+ buildCompositeAdapter("worker-runner-runtime")
2153
2149
  ];
2154
2150
  }
2155
2151
  export {
@@ -3,7 +3,7 @@ export type TreeseedReconcileProviderId = string;
3
3
  export type TreeseedReconcileActionKind = 'noop' | 'create' | 'update' | 'reuse' | 'drift_correct' | 'destroy';
4
4
  export type TreeseedReconcileStatusKind = 'pending' | 'ready' | 'drifted' | 'error';
5
5
  export type TreeseedReconcileVerificationSource = 'cli' | 'api' | 'sdk' | 'derived';
6
- export type TreeseedReconcileUnitType = 'web-ui' | 'api-runtime' | 'manager-runtime' | 'worker-runtime' | 'workday-start-runtime' | 'workday-report-runtime' | 'edge-worker' | 'content-store' | 'queue' | 'database' | 'kv-form-guard' | 'pages-project' | 'custom-domain:web' | 'custom-domain:api' | 'dns-record' | 'railway-service:api' | 'railway-service:manager' | 'railway-service:worker' | 'railway-service:workday-start' | 'railway-service:workday-report';
6
+ export type TreeseedReconcileUnitType = 'web-ui' | 'api-runtime' | 'workday-manager-runtime' | 'worker-runner-runtime' | 'edge-worker' | 'content-store' | 'queue' | 'database' | 'kv-form-guard' | 'pages-project' | 'custom-domain:web' | 'custom-domain:api' | 'dns-record' | 'railway-service:api' | 'railway-service:workday-manager' | 'railway-service:worker-runner';
7
7
  export type TreeseedReconcileTarget = {
8
8
  kind: 'persistent';
9
9
  scope: 'local' | 'staging' | 'prod';
@@ -11,14 +11,10 @@ function railwayConcreteUnitTypeForServiceKey(serviceKey) {
11
11
  switch (serviceKey) {
12
12
  case "api":
13
13
  return "railway-service:api";
14
- case "manager":
15
- return "railway-service:manager";
16
- case "worker":
17
- return "railway-service:worker";
18
- case "workdayStart":
19
- return "railway-service:workday-start";
20
- case "workdayReport":
21
- return "railway-service:workday-report";
14
+ case "workdayManager":
15
+ return "railway-service:workday-manager";
16
+ case "workerRunner":
17
+ return "railway-service:worker-runner";
22
18
  default:
23
19
  return "railway-service:api";
24
20
  }
@@ -227,7 +223,7 @@ function deriveTreeseedDesiredUnits({
227
223
  serviceKey,
228
224
  scheduleManaged: Array.isArray(configuredService.schedule) && configuredService.schedule.length > 0,
229
225
  scheduleBootstrap: false,
230
- scheduleDeployScopes: ["prod"],
226
+ scheduleDeployScopes: ["staging", "prod"],
231
227
  bootstrapSystem: serviceBootstrapSystem
232
228
  }
233
229
  });
@@ -270,14 +266,10 @@ function deriveTreeseedDesiredUnits({
270
266
  switch (serviceKey) {
271
267
  case "api":
272
268
  return "api-runtime";
273
- case "manager":
274
- return "manager-runtime";
275
- case "worker":
276
- return "worker-runtime";
277
- case "workdayStart":
278
- return "workday-start-runtime";
279
- case "workdayReport":
280
- return "workday-report-runtime";
269
+ case "workdayManager":
270
+ return "workday-manager-runtime";
271
+ case "workerRunner":
272
+ return "worker-runner-runtime";
281
273
  default:
282
274
  return "api-runtime";
283
275
  }
@@ -6,11 +6,11 @@ function stableHash(value) {
6
6
  return createHash("sha256").update(JSON.stringify(value)).digest("hex");
7
7
  }
8
8
  function railwayUnitTypeForServiceKey(serviceKey) {
9
- if (serviceKey === "workdayStart") {
10
- return "railway-service:workday-start";
9
+ if (serviceKey === "workdayManager") {
10
+ return "railway-service:workday-manager";
11
11
  }
12
- if (serviceKey === "workdayReport") {
13
- return "railway-service:workday-report";
12
+ if (serviceKey === "workerRunner") {
13
+ return "railway-service:worker-runner";
14
14
  }
15
15
  return `railway-service:${serviceKey}`;
16
16
  }
@@ -1,10 +1,8 @@
1
1
  const TRESEED_RECONCILE_UNIT_TYPES = [
2
2
  "web-ui",
3
3
  "api-runtime",
4
- "manager-runtime",
5
- "worker-runtime",
6
- "workday-start-runtime",
7
- "workday-report-runtime",
4
+ "workday-manager-runtime",
5
+ "worker-runner-runtime",
8
6
  "edge-worker",
9
7
  "content-store",
10
8
  "queue",
@@ -15,10 +13,8 @@ const TRESEED_RECONCILE_UNIT_TYPES = [
15
13
  "custom-domain:api",
16
14
  "dns-record",
17
15
  "railway-service:api",
18
- "railway-service:manager",
19
- "railway-service:worker",
20
- "railway-service:workday-start",
21
- "railway-service:workday-report"
16
+ "railway-service:workday-manager",
17
+ "railway-service:worker-runner"
22
18
  ];
23
19
  function targetKey(target) {
24
20
  return target.kind === "persistent" ? target.scope : `branch:${target.branchName}`;