@treeseed/sdk 0.4.13 → 0.5.1

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 (83) hide show
  1. package/dist/control-plane-client.d.ts +60 -1
  2. package/dist/control-plane-client.js +59 -0
  3. package/dist/control-plane.d.ts +1 -1
  4. package/dist/control-plane.js +11 -4
  5. package/dist/d1-store.d.ts +58 -0
  6. package/dist/d1-store.js +64 -0
  7. package/dist/dispatch.js +6 -0
  8. package/dist/graph/schema.js +4 -0
  9. package/dist/index.d.ts +5 -1
  10. package/dist/index.js +32 -0
  11. package/dist/knowledge-coop.d.ts +223 -0
  12. package/dist/knowledge-coop.js +82 -0
  13. package/dist/model-registry.js +79 -0
  14. package/dist/operations/providers/default.js +126 -7
  15. package/dist/operations/services/config-runtime.d.ts +102 -24
  16. package/dist/operations/services/config-runtime.js +896 -160
  17. package/dist/operations/services/deploy.d.ts +223 -15
  18. package/dist/operations/services/deploy.js +626 -55
  19. package/dist/operations/services/github-automation.d.ts +60 -0
  20. package/dist/operations/services/github-automation.js +138 -0
  21. package/dist/operations/services/key-agent.d.ts +118 -0
  22. package/dist/operations/services/key-agent.js +476 -0
  23. package/dist/operations/services/knowledge-coop-launch.d.ts +90 -0
  24. package/dist/operations/services/knowledge-coop-launch.js +753 -0
  25. package/dist/operations/services/knowledge-coop-packaging.d.ts +59 -0
  26. package/dist/operations/services/knowledge-coop-packaging.js +234 -0
  27. package/dist/operations/services/local-dev.d.ts +0 -1
  28. package/dist/operations/services/local-dev.js +1 -14
  29. package/dist/operations/services/project-platform.d.ts +42 -182
  30. package/dist/operations/services/project-platform.js +162 -59
  31. package/dist/operations/services/railway-deploy.d.ts +1 -0
  32. package/dist/operations/services/railway-deploy.js +31 -13
  33. package/dist/operations/services/runtime-tools.d.ts +52 -5
  34. package/dist/operations/services/runtime-tools.js +186 -26
  35. package/dist/operations/services/watch-dev.js +2 -4
  36. package/dist/operations/services/workspace-preflight.d.ts +4 -4
  37. package/dist/operations/services/workspace-preflight.js +22 -20
  38. package/dist/operations-registry.js +7 -2
  39. package/dist/platform/contracts.d.ts +39 -3
  40. package/dist/platform/deploy-config.d.ts +12 -1
  41. package/dist/platform/deploy-config.js +214 -15
  42. package/dist/platform/deploy-runtime.d.ts +1 -0
  43. package/dist/platform/deploy-runtime.js +10 -2
  44. package/dist/platform/env.yaml +93 -61
  45. package/dist/platform/environment.d.ts +13 -2
  46. package/dist/platform/environment.js +90 -20
  47. package/dist/platform/plugins/constants.d.ts +1 -0
  48. package/dist/platform/plugins/constants.js +7 -6
  49. package/dist/platform/tenant/runtime-config.js +8 -1
  50. package/dist/platform/tenant-config.js +4 -0
  51. package/dist/platform/utils/site-config-schema.js +18 -0
  52. package/dist/plugin-default.js +2 -2
  53. package/dist/scripts/key-agent.js +165 -0
  54. package/dist/scripts/tenant-build.js +4 -1
  55. package/dist/scripts/tenant-check.js +4 -1
  56. package/dist/scripts/tenant-deploy.js +43 -4
  57. package/dist/scripts/tenant-dev.js +0 -1
  58. package/dist/sdk-types.d.ts +2 -2
  59. package/dist/sdk-types.js +2 -0
  60. package/dist/sdk.d.ts +13 -0
  61. package/dist/sdk.js +40 -0
  62. package/dist/stores/knowledge-coop-store.d.ts +56 -0
  63. package/dist/stores/knowledge-coop-store.js +482 -0
  64. package/dist/treeseed/template-catalog/templates/starter-basic/template/package.json +6 -2
  65. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/api/server.js +4 -0
  66. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/config.yaml +25 -0
  67. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/decisions/adopt-initial-proposal-loop.mdx +22 -0
  68. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/people/starter-steward.mdx +11 -0
  69. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/proposals/establish-initial-proposal-loop.mdx +17 -0
  70. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/manifest.yaml +17 -10
  71. package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +69 -7
  72. package/dist/treeseed/template-catalog/templates/starter-basic/template.config.json +1 -0
  73. package/dist/verification.js +90 -2
  74. package/dist/workflow/operations.d.ts +98 -0
  75. package/dist/workflow/operations.js +229 -7
  76. package/dist/workflow-state.d.ts +54 -2
  77. package/dist/workflow-state.js +170 -24
  78. package/dist/workflow-support.d.ts +1 -1
  79. package/dist/workflow-support.js +32 -2
  80. package/dist/workflow.d.ts +29 -0
  81. package/package.json +1 -1
  82. package/templates/github/deploy.workflow.yml +11 -1
  83. package/dist/scripts/sync-dev-vars.js +0 -6
@@ -20,6 +20,7 @@ import {
20
20
  ensureGeneratedWranglerConfig,
21
21
  finalizeDeploymentState,
22
22
  loadDeployState,
23
+ purgePublishedContentCaches,
23
24
  provisionCloudflareResources,
24
25
  syncCloudflareSecrets,
25
26
  verifyProvisionedCloudflareResources,
@@ -175,14 +176,66 @@ function uploadObject(tenantRoot, wranglerPath, wranglerEnv, bucketName, pointer
175
176
  pointer.contentType ?? inferContentType(filePath)
176
177
  ], wranglerEnv);
177
178
  }
179
+ function deleteObject(tenantRoot, wranglerPath, wranglerEnv, bucketName, objectKey) {
180
+ runWrangler(tenantRoot, [
181
+ "r2",
182
+ "object",
183
+ "delete",
184
+ `${bucketName}/${objectKey}`,
185
+ "--config",
186
+ wranglerPath,
187
+ "--remote"
188
+ ], wranglerEnv, { allowFailure: true });
189
+ }
178
190
  function objectFileName(pointer) {
179
191
  const ext = extname(pointer.objectKey) || ".json";
180
192
  return `${pointer.sha256}${ext}`;
181
193
  }
182
- async function fetchJson(url, init = {}) {
183
- const response = await fetch(url, init);
184
- const body = await response.json().catch(() => null);
185
- return { response, body };
194
+ function canonicalSitePathForEntry(entry) {
195
+ return entry.model === "pages" ? `/${entry.slug || entry.id || ""}`.replace(/\/+$/u, "") || "/" : `/${entry.model}/${entry.slug || entry.id || ""}`.replace(/\/+$/u, "");
196
+ }
197
+ function contentIndexPathForModel(model) {
198
+ return model === "pages" ? null : `/${model}`;
199
+ }
200
+ function sourceLikeFreshnessPaths() {
201
+ return ["/", "/feed.xml", "/sitemap-index.xml"];
202
+ }
203
+ function entrySignature(entry) {
204
+ const { publishedAt, ...rest } = entry;
205
+ return stableHash(JSON.stringify(rest));
206
+ }
207
+ function artifactSignature(artifact) {
208
+ const { publishedAt, ...rest } = artifact;
209
+ return stableHash(JSON.stringify(rest));
210
+ }
211
+ function canonicalEntryPath(entry) {
212
+ return `${entry.model}/${entry.slug || entry.id || ""}`.replace(/^\/+|\/+$/gu, "");
213
+ }
214
+ function changedEntries(previousManifest, nextEntries) {
215
+ const previous = new Map((previousManifest?.entries ?? []).map((entry) => [canonicalEntryPath(entry), entrySignature(entry)]));
216
+ return nextEntries.filter((entry) => previous.get(canonicalEntryPath(entry)) !== entrySignature(entry));
217
+ }
218
+ function changedArtifacts(previousManifest, nextArtifacts) {
219
+ const previous = new Map((previousManifest?.artifacts ?? []).map((artifact) => [`${artifact.kind}:${artifact.itemId}`, artifactSignature(artifact)]));
220
+ return nextArtifacts.filter((artifact) => previous.get(`${artifact.kind}:${artifact.itemId}`) !== artifactSignature(artifact));
221
+ }
222
+ function absoluteUrl(baseUrl, path) {
223
+ if (!baseUrl) {
224
+ return null;
225
+ }
226
+ try {
227
+ return new URL(path.startsWith("/") ? path : `/${path}`, baseUrl).toString();
228
+ } catch {
229
+ return null;
230
+ }
231
+ }
232
+ function stableEntryAliasKey(teamId, entry) {
233
+ const ext = extname(entry.content.objectKey) || ".md";
234
+ return `teams/${teamId}/published/entries/${entry.model}/${entry.slug || entry.id}${ext}`;
235
+ }
236
+ function stableArtifactAliasKey(teamId, artifact) {
237
+ const fileName = typeof artifact.metadata?.fileName === "string" && artifact.metadata.fileName ? artifact.metadata.fileName : `${artifact.itemId}${extname(artifact.content.objectKey) || ".bin"}`;
238
+ return `teams/${teamId}/published/artifacts/${artifact.kind}/${artifact.itemId}/${fileName}`;
186
239
  }
187
240
  async function probeHttp(url) {
188
241
  try {
@@ -203,49 +256,8 @@ async function probeHttp(url) {
203
256
  };
204
257
  }
205
258
  }
206
- async function probeRunnerHealth(siteConfig, environment) {
207
- const baseUrl = String(process.env.TREESEED_MARKET_API_BASE_URL ?? siteConfig.hosting?.marketBaseUrl ?? "").trim();
208
- const projectId = String(process.env.TREESEED_PROJECT_ID ?? siteConfig.hosting?.projectId ?? "").trim();
209
- const runnerToken = String(process.env.TREESEED_PROJECT_RUNNER_TOKEN ?? "").trim();
210
- if (!baseUrl || !projectId || !runnerToken || environment === "local") {
211
- return { ok: true, skipped: true, reason: "runner_health_unconfigured" };
212
- }
213
- try {
214
- let lastResult = null;
215
- for (let attempt = 0; attempt < 10; attempt += 1) {
216
- const { response, body } = await fetchJson(
217
- `${baseUrl.replace(/\/+$/u, "")}/v1/projects/${encodeURIComponent(projectId)}/runner/health?environment=${encodeURIComponent(environment)}`,
218
- {
219
- headers: {
220
- accept: "application/json",
221
- authorization: `Bearer ${runnerToken}`
222
- }
223
- }
224
- );
225
- const pools = Array.isArray(body?.payload?.pools) ? body.payload.pools : [];
226
- const heartbeatPresent = pools.some((entry) => entry?.latestRegistration);
227
- lastResult = {
228
- ok: response.ok && body?.ok === true && heartbeatPresent,
229
- status: response.status,
230
- payload: body?.payload ?? null,
231
- heartbeatPresent,
232
- attempt: attempt + 1
233
- };
234
- if (lastResult.ok) {
235
- return lastResult;
236
- }
237
- await new Promise((resolve2) => setTimeout(resolve2, 3e3));
238
- }
239
- return lastResult ?? { ok: false, reason: "runner_health_unavailable" };
240
- } catch (error) {
241
- return {
242
- ok: false,
243
- error: error instanceof Error ? error.message : String(error)
244
- };
245
- }
246
- }
247
259
  function queueClientConfig(siteConfig, state) {
248
- const accountId = siteConfig.cloudflare.accountId;
260
+ const accountId = String(process.env.CLOUDFLARE_ACCOUNT_ID ?? siteConfig.cloudflare.accountId ?? "").trim();
249
261
  const queueId = state.queues?.agentWork?.queueId;
250
262
  const token = process.env.TREESEED_QUEUE_PUSH_TOKEN?.trim() || process.env.TREESEED_QUEUE_PULL_TOKEN?.trim() || process.env.CLOUDFLARE_API_TOKEN?.trim() || "";
251
263
  if (!accountId || !queueId || !token) {
@@ -310,11 +322,12 @@ function deleteR2Object(tenantRoot, bucketName, objectKey, wranglerPath, wrangle
310
322
  }
311
323
  function probeR2(tenantRoot, siteConfig, state, target) {
312
324
  const bucketName = state.content?.bucketName;
325
+ const cloudflareAccountId = String(process.env.CLOUDFLARE_ACCOUNT_ID ?? siteConfig.cloudflare.accountId ?? "").trim();
313
326
  if (!bucketName) {
314
327
  return { ok: false, skipped: true, reason: "r2_unconfigured" };
315
328
  }
316
329
  const { wranglerPath } = ensureGeneratedWranglerConfig(tenantRoot, { target });
317
- const wranglerEnv = { CLOUDFLARE_ACCOUNT_ID: siteConfig.cloudflare.accountId };
330
+ const wranglerEnv = { CLOUDFLARE_ACCOUNT_ID: cloudflareAccountId };
318
331
  const tempRoot = mkdtempSync(join(tmpdir(), "treeseed-r2-health-"));
319
332
  const objectKey = r2HealthKey(state);
320
333
  try {
@@ -358,7 +371,7 @@ function probeScaleConfiguration(siteConfig, state) {
358
371
  async function publishContent(options, reporter) {
359
372
  const siteConfig = loadCliDeployConfig(options.tenantRoot);
360
373
  const tenantConfig = loadTreeseedManifest(resolve(options.tenantRoot, "src", "manifest.yaml"));
361
- const teamId = siteConfig.hosting?.teamId ?? siteConfig.slug;
374
+ const teamId = String(process.env.TREESEED_HOSTING_TEAM_ID ?? siteConfig.hosting?.teamId ?? siteConfig.slug).trim() || siteConfig.slug;
362
375
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
363
376
  const commitSha = currentCommit(options.tenantRoot);
364
377
  const branchName = currentRef(options.tenantRoot);
@@ -366,10 +379,10 @@ async function publishContent(options, reporter) {
366
379
  const locator = resolveTeamScopedContentLocator(siteConfig, teamId);
367
380
  const target = createPersistentDeployTarget(options.scope === "local" ? "staging" : options.scope);
368
381
  const { wranglerPath } = ensureGeneratedWranglerConfig(options.tenantRoot, { target });
369
- const wranglerEnv = { CLOUDFLARE_ACCOUNT_ID: siteConfig.cloudflare.accountId };
370
- const bucketName = siteConfig.cloudflare.r2?.bucketName;
382
+ const wranglerEnv = { CLOUDFLARE_ACCOUNT_ID: String(process.env.CLOUDFLARE_ACCOUNT_ID ?? siteConfig.cloudflare.accountId ?? "").trim() };
383
+ const bucketName = String(process.env.TREESEED_CONTENT_BUCKET_NAME ?? siteConfig.cloudflare.r2?.bucketName ?? "").trim();
371
384
  if (!bucketName) {
372
- throw new Error("Treeseed content publish requires cloudflare.r2.bucketName in treeseed.site.yaml.");
385
+ throw new Error("Treeseed content publish requires TREESEED_CONTENT_BUCKET_NAME to be configured.");
373
386
  }
374
387
  const previousManifest = readR2JsonObject(
375
388
  options.tenantRoot,
@@ -389,6 +402,85 @@ async function publishContent(options, reporter) {
389
402
  previewId
390
403
  });
391
404
  const built = options.scope === "staging" ? await pipeline.buildEditorialOverlay({ previousManifest, previewId }) : await pipeline.buildProductionRevision({ previousManifest });
405
+ const changedEntrySet = "manifest" in built ? changedEntries(previousManifest, built.manifest.entries) : [];
406
+ const changedArtifactSet = "manifest" in built ? changedArtifacts(previousManifest, built.manifest.artifacts ?? []) : [];
407
+ const tombstones = "manifest" in built ? built.manifest.tombstones ?? [] : [];
408
+ const objectByKey = new Map(built.objects.map((object) => [object.pointer.objectKey, object]));
409
+ const publicObjectBaseUrl = process.env.TREESEED_CONTENT_PUBLIC_BASE_URL ?? siteConfig.cloudflare.r2?.publicBaseUrl ?? null;
410
+ const stableObjectUploads = [];
411
+ const deletedObjectKeys = [];
412
+ const contentPurgeUrls = /* @__PURE__ */ new Set();
413
+ if ("manifest" in built && publicObjectBaseUrl) {
414
+ for (const entry of built.manifest.entries) {
415
+ entry.content.publicUrl = absoluteUrl(publicObjectBaseUrl, stableEntryAliasKey(teamId, entry)) ?? void 0;
416
+ }
417
+ for (const artifact of built.manifest.artifacts ?? []) {
418
+ artifact.content.publicUrl = artifact.content.publicUrl ?? absoluteUrl(publicObjectBaseUrl, stableArtifactAliasKey(teamId, artifact)) ?? void 0;
419
+ }
420
+ for (const entry of changedEntrySet) {
421
+ const current = entry;
422
+ const sourceObject = objectByKey.get(current.content.objectKey);
423
+ const publicUrl = current.content.publicUrl ?? absoluteUrl(publicObjectBaseUrl, stableEntryAliasKey(teamId, current));
424
+ if (sourceObject) {
425
+ stableObjectUploads.push({
426
+ pointer: {
427
+ objectKey: stableEntryAliasKey(teamId, current),
428
+ sha256: sourceObject.pointer.sha256,
429
+ size: sourceObject.pointer.size,
430
+ contentType: sourceObject.pointer.contentType,
431
+ publicUrl: publicUrl ?? void 0
432
+ },
433
+ body: toBuffer(sourceObject.body)
434
+ });
435
+ }
436
+ if (publicUrl) {
437
+ contentPurgeUrls.add(publicUrl);
438
+ }
439
+ contentPurgeUrls.add(absoluteUrl(siteConfig.siteUrl, canonicalSitePathForEntry(current)) ?? "");
440
+ const indexPath = contentIndexPathForModel(current.model);
441
+ if (indexPath) {
442
+ contentPurgeUrls.add(absoluteUrl(siteConfig.siteUrl, indexPath) ?? "");
443
+ }
444
+ }
445
+ for (const artifact of changedArtifactSet) {
446
+ const current = artifact;
447
+ const sourceObject = objectByKey.get(current.content.objectKey);
448
+ const publicUrl = current.content.publicUrl ?? absoluteUrl(publicObjectBaseUrl, stableArtifactAliasKey(teamId, current));
449
+ if (sourceObject && publicUrl) {
450
+ stableObjectUploads.push({
451
+ pointer: {
452
+ objectKey: stableArtifactAliasKey(teamId, current),
453
+ sha256: sourceObject.pointer.sha256,
454
+ size: sourceObject.pointer.size,
455
+ contentType: sourceObject.pointer.contentType,
456
+ publicUrl
457
+ },
458
+ body: toBuffer(sourceObject.body)
459
+ });
460
+ }
461
+ if (publicUrl) {
462
+ contentPurgeUrls.add(publicUrl);
463
+ }
464
+ }
465
+ for (const tombstone of tombstones) {
466
+ const [model, ...slugParts] = String(tombstone.path ?? "").split("/");
467
+ if (!model || slugParts.length === 0) {
468
+ continue;
469
+ }
470
+ const slug = slugParts.join("/");
471
+ const aliasKey = stableEntryAliasKey(teamId, { model, slug, content: { objectKey: `${slug}.md` } });
472
+ deletedObjectKeys.push(aliasKey);
473
+ contentPurgeUrls.add(absoluteUrl(publicObjectBaseUrl, aliasKey) ?? "");
474
+ contentPurgeUrls.add(absoluteUrl(siteConfig.siteUrl, canonicalSitePathForEntry({ model, slug })) ?? "");
475
+ const indexPath = contentIndexPathForModel(model);
476
+ if (indexPath) {
477
+ contentPurgeUrls.add(absoluteUrl(siteConfig.siteUrl, indexPath) ?? "");
478
+ }
479
+ }
480
+ for (const freshnessPath of sourceLikeFreshnessPaths()) {
481
+ contentPurgeUrls.add(absoluteUrl(siteConfig.siteUrl, freshnessPath) ?? "");
482
+ }
483
+ }
392
484
  const tempRoot = mkdtempSync(join(tmpdir(), "treeseed-content-publish-"));
393
485
  try {
394
486
  if (!options.dryRun) {
@@ -396,6 +488,13 @@ async function publishContent(options, reporter) {
396
488
  const filePath = writeTempFile(tempRoot, objectFileName(object.pointer), toBuffer(object.body));
397
489
  uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, object.pointer, filePath);
398
490
  }
491
+ for (const alias of stableObjectUploads) {
492
+ const filePath = writeTempFile(tempRoot, objectFileName(alias.pointer), alias.body);
493
+ uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, alias.pointer, filePath);
494
+ }
495
+ for (const objectKey of deletedObjectKeys) {
496
+ deleteObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, objectKey);
497
+ }
399
498
  if ("overlay" in built) {
400
499
  const overlayFile = writeTempFile(tempRoot, "overlay.json", Buffer.from(JSON.stringify(built.overlay, null, 2)));
401
500
  uploadObject(
@@ -426,6 +525,12 @@ async function publishContent(options, reporter) {
426
525
  size: statSync(manifestFile).size,
427
526
  contentType: "application/json"
428
527
  }, manifestFile);
528
+ if (contentPurgeUrls.size > 0) {
529
+ try {
530
+ purgePublishedContentCaches(options.tenantRoot, [...contentPurgeUrls].filter(Boolean), { target });
531
+ } catch {
532
+ }
533
+ }
429
534
  }
430
535
  }
431
536
  const state = loadDeployState(options.tenantRoot, siteConfig, { target });
@@ -455,7 +560,8 @@ async function publishContent(options, reporter) {
455
560
  previewUrl,
456
561
  entries: ("overlay" in built ? built.overlay.entries : built.manifest.entries).length,
457
562
  artifacts: ("overlay" in built ? built.overlay.artifacts : built.manifest.artifacts)?.length ?? 0,
458
- catalog: built.catalog.length
563
+ catalog: built.catalog.length,
564
+ cachePurgeCount: contentPurgeUrls.size
459
565
  },
460
566
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
461
567
  });
@@ -489,7 +595,7 @@ async function provisionProjectPlatform(options) {
489
595
  environment: options.scope,
490
596
  deploymentProfile: siteConfig.hosting?.kind ?? "self_hosted_project",
491
597
  baseUrl: state.lastDeployedUrl,
492
- cloudflareAccountId: siteConfig.cloudflare.accountId,
598
+ cloudflareAccountId: String(process.env.CLOUDFLARE_ACCOUNT_ID ?? siteConfig.cloudflare.accountId ?? "").trim() || null,
493
599
  pagesProjectName: state.pages?.projectName ?? null,
494
600
  workerName: state.workerName,
495
601
  r2BucketName: state.content?.bucketName ?? null,
@@ -666,7 +772,7 @@ async function monitorProjectPlatform(options) {
666
772
  const target = createPersistentDeployTarget(options.scope === "local" ? "staging" : options.scope);
667
773
  const siteConfig = loadCliDeployConfig(options.tenantRoot);
668
774
  const state = loadDeployState(options.tenantRoot, siteConfig, { target });
669
- const apiBaseUrl = siteConfig.services?.api?.environments?.[options.scope]?.baseUrl ?? siteConfig.services?.api?.publicBaseUrl ?? state.services?.api?.lastDeployedUrl ?? null;
775
+ const apiBaseUrl = siteConfig.services?.api?.environments?.[options.scope]?.baseUrl ?? siteConfig.services?.api?.publicBaseUrl ?? process.env.TREESEED_API_BASE_URL ?? state.services?.api?.lastDeployedUrl ?? null;
670
776
  const checks = {
671
777
  pages: await probeHttp(state.pages?.url ?? siteConfig.siteUrl),
672
778
  apiHealth: apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/healthz`) : { ok: false, skipped: true, reason: "api_url_unconfigured" },
@@ -675,15 +781,13 @@ async function monitorProjectPlatform(options) {
675
781
  agentHealth: apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/agent/healthz`) : { ok: false, skipped: true, reason: "api_url_unconfigured" },
676
782
  r2: options.dryRun ? { ok: true, skipped: true, reason: "dry_run" } : probeR2(options.tenantRoot, siteConfig, state, target),
677
783
  queue: options.dryRun ? Promise.resolve({ ok: true, skipped: true, reason: "dry_run" }) : probeQueue(siteConfig, state),
678
- runner: probeRunnerHealth(siteConfig, options.scope),
679
784
  scaleProbe: probeScaleConfiguration(siteConfig, state),
680
785
  readiness: state.readiness
681
786
  };
682
787
  const resolvedChecks = {
683
788
  ...checks,
684
789
  r2: await checks.r2,
685
- queue: await checks.queue,
686
- runner: await checks.runner
790
+ queue: await checks.queue
687
791
  };
688
792
  const ok = [
689
793
  resolvedChecks.pages,
@@ -693,7 +797,6 @@ async function monitorProjectPlatform(options) {
693
797
  resolvedChecks.agentHealth,
694
798
  resolvedChecks.r2,
695
799
  resolvedChecks.queue,
696
- resolvedChecks.runner,
697
800
  resolvedChecks.scaleProbe
698
801
  ].every((check) => check?.ok === true || check?.skipped === true);
699
802
  if (!ok) {
@@ -728,7 +831,7 @@ async function syncControlPlaneState(options) {
728
831
  environment: options.scope,
729
832
  deploymentProfile: siteConfig.hosting?.kind ?? "self_hosted_project",
730
833
  baseUrl: state.lastDeployedUrl,
731
- cloudflareAccountId: siteConfig.cloudflare.accountId,
834
+ cloudflareAccountId: String(process.env.CLOUDFLARE_ACCOUNT_ID ?? siteConfig.cloudflare.accountId ?? "").trim() || null,
732
835
  pagesProjectName: state.pages?.projectName ?? null,
733
836
  workerName: state.workerName,
734
837
  r2BucketName: state.content?.bucketName ?? null,
@@ -119,6 +119,7 @@ export declare function verifyRailwayScheduledJobs(tenantRoot: any, scope: any,
119
119
  checks: {
120
120
  id: any;
121
121
  ok: boolean;
122
+ status: string;
122
123
  service: string;
123
124
  projectId: string | null;
124
125
  projectName: string | null;
@@ -6,8 +6,8 @@ const DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
6
6
  function normalizeScope(scope) {
7
7
  return scope === "prod" ? "prod" : scope === "staging" ? "staging" : "local";
8
8
  }
9
- const RAILWAY_SERVICE_KEYS = ["api", "agents", "manager", "worker", "runner", "workdayStart", "workdayReport"];
10
- const HOSTED_PROJECT_SERVICE_KEYS = ["api", "manager", "worker", "agents"];
9
+ const RAILWAY_SERVICE_KEYS = ["api", "manager", "worker", "workdayStart", "workdayReport"];
10
+ const HOSTED_PROJECT_SERVICE_KEYS = ["api", "manager", "worker"];
11
11
  function normalizeScheduleExpressions(value) {
12
12
  if (typeof value === "string" && value.trim()) {
13
13
  return [value.trim()];
@@ -179,14 +179,18 @@ function runRailway(args, { cwd, capture = false, allowFailure = false } = {}) {
179
179
  function configuredRailwayServices(tenantRoot, scope) {
180
180
  const deployConfig = loadCliDeployConfig(tenantRoot);
181
181
  const normalizedScope = normalizeScope(scope);
182
- const hostingKind = deployConfig.hosting?.kind ?? "self_hosted_project";
182
+ const managedRuntime = deployConfig.runtime?.mode === "treeseed_managed";
183
+ const hostingKind = deployConfig.hosting?.kind ?? (managedRuntime ? "hosted_project" : "self_hosted_project");
184
+ if (!managedRuntime) {
185
+ return [];
186
+ }
183
187
  const serviceKeys = hostingKind === "hosted_project" ? HOSTED_PROJECT_SERVICE_KEYS : RAILWAY_SERVICE_KEYS;
184
188
  return serviceKeys.map((serviceKey) => {
185
189
  const service = deployConfig.services?.[serviceKey];
186
190
  if (!service || service.enabled === false || (service.provider ?? "railway") !== "railway") {
187
191
  return null;
188
192
  }
189
- const defaultRootDir = ["api", "manager", "worker", "runner", "workdayStart", "workdayReport"].includes(serviceKey) ? "." : "packages/core";
193
+ const defaultRootDir = ["api", "manager", "worker", "workdayStart", "workdayReport"].includes(serviceKey) ? "." : "packages/core";
190
194
  const serviceRoot = resolve(tenantRoot, service.railway?.rootDir ?? service.rootDir ?? defaultRootDir);
191
195
  const railwayEnvironment = service.environments?.[normalizedScope]?.railwayEnvironment ?? normalizedScope;
192
196
  const publicBaseUrl = service.environments?.[normalizedScope]?.baseUrl ?? service.publicBaseUrl ?? null;
@@ -226,10 +230,10 @@ function configuredRailwayScheduledJobs(tenantRoot, scope) {
226
230
  }
227
231
  function resolveRailwayDeploymentProfile(tenantRoot) {
228
232
  const deployConfig = loadCliDeployConfig(tenantRoot);
229
- const hostingKind = deployConfig.hosting?.kind ?? "self_hosted_project";
233
+ const hostingKind = deployConfig.hosting?.kind ?? (deployConfig.runtime?.mode === "treeseed_managed" ? "hosted_project" : "self_hosted_project");
230
234
  return {
231
235
  hostingKind,
232
- managedTopology: hostingKind === "hosted_project" ? [...HOSTED_PROJECT_SERVICE_KEYS] : [...RAILWAY_SERVICE_KEYS]
236
+ managedTopology: deployConfig.runtime?.mode === "treeseed_managed" ? hostingKind === "hosted_project" ? [...HOSTED_PROJECT_SERVICE_KEYS] : [...RAILWAY_SERVICE_KEYS] : []
233
237
  };
234
238
  }
235
239
  function validateRailwayServiceConfiguration(tenantRoot, scope) {
@@ -257,12 +261,6 @@ function validateRailwayServiceConfiguration(tenantRoot, scope) {
257
261
  if (service.schedule?.length && !service.startCommand) {
258
262
  issues.push(`${service.key}: scheduled Railway services require railway.startCommand in treeseed.site.yaml.`);
259
263
  }
260
- if (service.schedule?.length && !service.serviceId) {
261
- issues.push(`${service.key}: scheduled Railway services require railway.serviceId for Railway API reconciliation.`);
262
- }
263
- if (service.schedule?.length && !envValue("TREESEED_RAILWAY_ENVIRONMENT_ID")) {
264
- issues.push(`${service.key}: scheduled Railway services require TREESEED_RAILWAY_ENVIRONMENT_ID to be configured.`);
265
- }
266
264
  }
267
265
  if (issues.length > 0) {
268
266
  throw new Error(`Railway service configuration is incomplete:
@@ -288,6 +286,16 @@ async function ensureRailwayScheduledJobs(tenantRoot, scope, { dryRun = false, f
288
286
  const queries = defaultRailwayScheduleQueries();
289
287
  const results = [];
290
288
  for (const schedule of schedules) {
289
+ if (!schedule.serviceId || !schedule.environmentId) {
290
+ results.push({
291
+ ...schedule,
292
+ id: null,
293
+ status: "skipped_missing_identifiers",
294
+ enabled: schedule.enabled !== false,
295
+ command: schedule.command
296
+ });
297
+ continue;
298
+ }
291
299
  const variables = {
292
300
  projectId: schedule.projectId,
293
301
  serviceId: schedule.serviceId,
@@ -391,6 +399,15 @@ async function verifyRailwayScheduledJobs(tenantRoot, scope, { fetchImpl = fetch
391
399
  const queries = defaultRailwayScheduleQueries();
392
400
  const checks = [];
393
401
  for (const schedule of configured) {
402
+ if (!schedule.serviceId || !schedule.environmentId) {
403
+ checks.push({
404
+ ...schedule,
405
+ id: null,
406
+ ok: false,
407
+ status: "skipped_missing_identifiers"
408
+ });
409
+ continue;
410
+ }
394
411
  const listed = await railwayGraphqlRequest({
395
412
  query: queries.listQuery,
396
413
  variables: {
@@ -410,7 +427,8 @@ async function verifyRailwayScheduledJobs(tenantRoot, scope, { fetchImpl = fetch
410
427
  id: existing?.id ?? null,
411
428
  ok: Boolean(
412
429
  existing && existing.expression === schedule.expression && (existing.command ?? null) === (schedule.command ?? null) && existing.enabled !== false
413
- )
430
+ ),
431
+ status: existing ? "checked" : "missing"
414
432
  });
415
433
  }
416
434
  return {
@@ -37,13 +37,24 @@ export declare function loadCliDeployConfig(tenantRoot: any): {
37
37
  contactEmail: string;
38
38
  hosting: {
39
39
  kind: string;
40
- registration: string;
41
- marketBaseUrl: string | undefined;
42
- teamId: string | undefined;
43
- projectId: string | undefined;
44
- } | undefined;
40
+ registration: any;
41
+ marketBaseUrl: any;
42
+ teamId: any;
43
+ projectId: any;
44
+ };
45
+ hub: {
46
+ mode: string;
47
+ };
48
+ runtime: {
49
+ mode: string;
50
+ registration: any;
51
+ marketBaseUrl: any;
52
+ teamId: any;
53
+ projectId: any;
54
+ };
45
55
  cloudflare: {
46
56
  accountId: string;
57
+ zoneId: string | undefined;
47
58
  workerName: string | undefined;
48
59
  queueName: string | undefined;
49
60
  dlqName: string | undefined;
@@ -88,9 +99,45 @@ export declare function loadCliDeployConfig(tenantRoot: any): {
88
99
  runtime: string;
89
100
  publish: string;
90
101
  docs: string;
102
+ serving: any;
91
103
  };
92
104
  site: string;
93
105
  };
106
+ surfaces: {
107
+ [k: string]: {
108
+ enabled: boolean | undefined;
109
+ provider: string | undefined;
110
+ rootDir: string | undefined;
111
+ publicBaseUrl: string | undefined;
112
+ localBaseUrl: string | undefined;
113
+ cache: {
114
+ sourcePages: {
115
+ browserTtlSeconds: number | undefined;
116
+ edgeTtlSeconds: number | undefined;
117
+ staleWhileRevalidateSeconds: number | undefined;
118
+ staleIfErrorSeconds: number | undefined;
119
+ } | {
120
+ paths: any[];
121
+ } | undefined;
122
+ contentPages: {
123
+ browserTtlSeconds: number | undefined;
124
+ edgeTtlSeconds: number | undefined;
125
+ staleWhileRevalidateSeconds: number | undefined;
126
+ staleIfErrorSeconds: number | undefined;
127
+ } | {
128
+ paths: any[];
129
+ } | undefined;
130
+ r2PublishedObjects: {
131
+ browserTtlSeconds: number | undefined;
132
+ edgeTtlSeconds: number | undefined;
133
+ staleWhileRevalidateSeconds: number | undefined;
134
+ staleIfErrorSeconds: number | undefined;
135
+ } | {
136
+ paths: any[];
137
+ } | undefined;
138
+ } | undefined;
139
+ } | undefined;
140
+ } | undefined;
94
141
  services: {
95
142
  [k: string]: {
96
143
  enabled: boolean | undefined;