@treeseed/sdk 0.10.21 → 0.10.23

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 (34) hide show
  1. package/dist/db/market-schema.js +3 -2
  2. package/dist/market-client.d.ts +4 -0
  3. package/dist/market-client.js +6 -0
  4. package/dist/operations/providers/default.js +26 -4
  5. package/dist/operations/repository-operations.js +6 -2
  6. package/dist/operations/services/config-runtime.d.ts +1 -1
  7. package/dist/operations/services/deploy.d.ts +20 -3
  8. package/dist/operations/services/deploy.js +228 -102
  9. package/dist/operations/services/github-automation.d.ts +10 -1
  10. package/dist/operations/services/github-automation.js +18 -4
  11. package/dist/operations/services/hosting-audit.d.ts +2 -1
  12. package/dist/operations/services/hosting-audit.js +12 -1
  13. package/dist/operations/services/hub-launch.d.ts +1 -0
  14. package/dist/operations/services/hub-launch.js +1 -0
  15. package/dist/operations/services/hub-provider-launch.d.ts +9 -0
  16. package/dist/operations/services/hub-provider-launch.js +140 -40
  17. package/dist/operations/services/managed-host-security.d.ts +1 -1
  18. package/dist/operations/services/managed-host-security.js +4 -1
  19. package/dist/operations/services/project-platform.js +16 -0
  20. package/dist/operations/services/railway-api.js +56 -8
  21. package/dist/operations/services/railway-deploy.d.ts +2 -1
  22. package/dist/operations/services/railway-deploy.js +15 -18
  23. package/dist/platform/environment.d.ts +1 -1
  24. package/dist/platform/environment.js +1 -1
  25. package/dist/reconcile/builtin-adapters.js +155 -25
  26. package/dist/reconcile/contracts.d.ts +1 -1
  27. package/dist/reconcile/desired-state.js +17 -1
  28. package/dist/reconcile/units.js +1 -0
  29. package/dist/sdk-types.d.ts +1 -1
  30. package/dist/sdk-types.js +2 -0
  31. package/dist/workflow/operations.d.ts +2 -0
  32. package/drizzle/market/0000_market_control_plane.sql +3 -3
  33. package/drizzle/market/0003_project_team_slug_unique.sql +4 -0
  34. package/package.json +1 -1
@@ -53,6 +53,7 @@ export type TreeseedHostingAuditOptions = {
53
53
  valuesOverlay?: Record<string, string | undefined>;
54
54
  hostKinds?: TreeseedHostingAuditHostKind[];
55
55
  providerConnectionChecks?: boolean;
56
+ resourceChecks?: boolean;
56
57
  write?: (line: string) => void;
57
58
  };
58
59
  export declare function resolveTreeseedHostingAuditTarget({ tenantRoot, environment, }: {
@@ -64,5 +65,5 @@ export declare function resolveTreeseedHostingAuditTarget({ tenantRoot, environm
64
65
  target: TreeseedReconcileTarget;
65
66
  branchName: string | null;
66
67
  };
67
- export declare function runTreeseedHostingAudit({ tenantRoot, environment, repair, env, valuesOverlay, hostKinds: requestedHostKinds, providerConnectionChecks: shouldCheckProviderConnections, write, }: TreeseedHostingAuditOptions): Promise<TreeseedHostingAuditReport>;
68
+ export declare function runTreeseedHostingAudit({ tenantRoot, environment, repair, env, valuesOverlay, hostKinds: requestedHostKinds, providerConnectionChecks: shouldCheckProviderConnections, resourceChecks: shouldCheckResources, write, }: TreeseedHostingAuditOptions): Promise<TreeseedHostingAuditReport>;
68
69
  export declare function formatTreeseedHostingAuditReport(report: TreeseedHostingAuditReport): string;
@@ -463,6 +463,7 @@ async function runTreeseedHostingAudit({
463
463
  valuesOverlay = {},
464
464
  hostKinds: requestedHostKinds,
465
465
  providerConnectionChecks: shouldCheckProviderConnections = true,
466
+ resourceChecks: shouldCheckResources = true,
466
467
  write
467
468
  }) {
468
469
  const resolved = resolveTreeseedHostingAuditTarget({ tenantRoot, environment });
@@ -509,7 +510,17 @@ async function runTreeseedHostingAudit({
509
510
  const systems = reconcileSystemsForHostKinds(hostKinds);
510
511
  let resources = {};
511
512
  let repaired = false;
512
- if (resolved.environment !== "local" && systems.length > 0) {
513
+ if (!shouldCheckResources) {
514
+ checks.push({
515
+ id: "resources.skipped",
516
+ hostType: "platform",
517
+ provider: "treeseed",
518
+ category: "resource",
519
+ status: "skipped",
520
+ severity: "info",
521
+ summary: "Hosted provider resource checks are skipped for this audit."
522
+ });
523
+ } else if (resolved.environment !== "local" && systems.length > 0) {
513
524
  try {
514
525
  const state = loadDeployState(tenantRoot, deployConfig, { target: resolved.target });
515
526
  resources = buildProvisioningSummary(deployConfig, state, resolved.target);
@@ -42,6 +42,7 @@ export interface KnowledgeHubLaunchIntent {
42
42
  name: string;
43
43
  slug: string;
44
44
  purpose?: string | null;
45
+ coreObjective?: string | null;
45
46
  visibility?: 'private' | 'team' | 'public';
46
47
  };
47
48
  source?: {
@@ -193,6 +193,7 @@ function providerLaunchInputFromIntent(plan) {
193
193
  projectSlug: intent.hub.slug,
194
194
  projectName: intent.hub.name,
195
195
  summary: intent.hub.purpose ?? null,
196
+ coreObjective: providerInput.coreObjective ?? intent.hub.coreObjective ?? intent.hub.purpose ?? null,
196
197
  sourceKind: sourceKind === "blank_hub" ? "blank" : sourceKind === "market_listing" ? "template" : sourceKind,
197
198
  sourceRef: intent.source?.ref ?? null,
198
199
  hostingMode: intent.hosting?.mode === "treeseed_managed" ? "managed" : intent.hosting?.mode ?? "managed",
@@ -9,6 +9,7 @@ export interface KnowledgeHubProviderLaunchInput {
9
9
  projectSlug: string;
10
10
  projectName: string;
11
11
  summary?: string | null;
12
+ coreObjective?: string | null;
12
13
  sourceKind: 'blank' | 'template' | 'knowledge_pack';
13
14
  sourceRef?: string | null;
14
15
  hostingMode?: 'managed' | 'hybrid' | 'self_hosted';
@@ -38,6 +39,14 @@ export interface KnowledgeHubProviderLaunchInput {
38
39
  enableDefaultAgents?: boolean;
39
40
  preserveWorkingTree?: boolean;
40
41
  cloudflareHost?: KnowledgeHubCloudflareHostLaunchInput | null;
42
+ domains?: {
43
+ productionDomain?: string | null;
44
+ stagingDomain?: string | null;
45
+ zoneName?: string | null;
46
+ zoneId?: string | null;
47
+ manageDns?: boolean;
48
+ provider?: string | null;
49
+ } | null;
41
50
  }
42
51
  export interface KnowledgeHubCloudflareHostConfig {
43
52
  CLOUDFLARE_API_TOKEN?: string;
@@ -10,6 +10,7 @@ import {
10
10
  createGitHubRepository,
11
11
  ensureGitHubDeployAutomation,
12
12
  initializeGitHubRepositoryWorkingTree,
13
+ resolveGitHubRemoteUrls,
13
14
  resolveDefaultGitHubOwner
14
15
  } from "./github-automation.js";
15
16
  import { configuredRailwayServices, deployRailwayService, ensureRailwayScheduledJobs, validateRailwayDeployPrerequisites, verifyRailwayScheduledJobs } from "./railway-deploy.js";
@@ -41,6 +42,10 @@ function envOrNull(name) {
41
42
  function normalizeBaseUrl(value) {
42
43
  return String(value ?? "").trim().replace(/\/+$/u, "");
43
44
  }
45
+ function domainUrl(domain) {
46
+ const value = String(domain ?? "").trim().replace(/^https?:\/\//u, "").replace(/\/+$/u, "");
47
+ return value ? `https://${value}` : null;
48
+ }
44
49
  function resolveManagedWebUrl(slug) {
45
50
  const baseDomain = envOrNull("TREESEED_MANAGED_WEB_BASE_DOMAIN");
46
51
  if (baseDomain) {
@@ -65,10 +70,25 @@ function runGit(cwd, args, capture = true) {
65
70
  encoding: "utf8"
66
71
  });
67
72
  if (result.status !== 0) {
68
- throw new Error(result.stderr?.trim() || result.stdout?.trim() || `git ${args.join(" ")} failed`);
73
+ if (args[0] === "push" && !args.includes("--force")) {
74
+ const retryArgs = ["push", "--force", ...args.slice(1)];
75
+ const retry = spawnSync("git", retryArgs, {
76
+ cwd,
77
+ stdio: capture ? "pipe" : "inherit",
78
+ encoding: "utf8"
79
+ });
80
+ if (retry.status === 0) return retry;
81
+ const retryDetail = retry.stderr?.trim() || retry.stdout?.trim();
82
+ throw new Error(`git ${retryArgs.join(" ")} failed${retryDetail ? `: ${retryDetail}` : ""}`);
83
+ }
84
+ const detail = result.stderr?.trim() || result.stdout?.trim();
85
+ throw new Error(`git ${args.join(" ")} failed${detail ? `: ${detail}` : ""}`);
69
86
  }
70
87
  return result;
71
88
  }
89
+ function gitOutput(cwd, args) {
90
+ return runGit(cwd, args, true).stdout?.trim() ?? "";
91
+ }
72
92
  function writeText(path, body) {
73
93
  ensureDir(dirname(path));
74
94
  writeFileSync(path, body, "utf8");
@@ -81,6 +101,20 @@ function updateYamlFile(path, updater) {
81
101
  function currentTemplateCatalogUrl() {
82
102
  return `file:${resolve(templateCatalogRoot, "catalog.fixture.json")}`;
83
103
  }
104
+ function frontmatter(fields) {
105
+ return `---
106
+ ${stringifyYaml(fields).trim()}
107
+ ---`;
108
+ }
109
+ function normalizeMarkdownBody(value, fallback) {
110
+ const markdown = String(value ?? "").trim();
111
+ return markdown || fallback;
112
+ }
113
+ function markdownToSummary(markdown, fallback) {
114
+ const text = markdown.replace(/^---[\s\S]*?---/u, " ").replace(/```[\s\S]*?```/gu, " ").replace(/`([^`]+)`/gu, "$1").replace(/!\[[^\]]*\]\([^)]+\)/gu, " ").replace(/\[([^\]]+)\]\([^)]+\)/gu, "$1").replace(/^#{1,6}\s+/gmu, "").replace(/^\s*[-*+]\s+/gmu, "").replace(/^\s*\d+\.\s+/gmu, "").replace(/[*_~>#]/gu, "").replace(/\s+/gu, " ").trim();
115
+ if (!text) return fallback;
116
+ return text.length > 240 ? `${text.slice(0, 237).trimEnd()}...` : text;
117
+ }
84
118
  function seedLaunchContent(projectRoot, input) {
85
119
  const objectiveId = `objective:launch-${slugify(input.projectSlug, "hub")}`;
86
120
  const questionId = `question:operating-${slugify(input.projectSlug, "hub")}`;
@@ -88,6 +122,13 @@ function seedLaunchContent(projectRoot, input) {
88
122
  const decisionId = `decision:launch-${slugify(input.projectSlug, "hub")}`;
89
123
  const stewardSlug = "launch-steward";
90
124
  const noteSlug = `${slugify(input.projectSlug, "hub")}-operating-model`;
125
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
126
+ const defaultCoreObjective = `# Core Objective
127
+
128
+ Build and maintain ${input.projectName} as a living TreeSeed project with clear direction, active work, reliable releases, and useful AI agent context.
129
+ `;
130
+ const coreObjective = normalizeMarkdownBody(input.coreObjective ?? input.summary, defaultCoreObjective);
131
+ const coreObjectiveSummary = markdownToSummary(coreObjective, `Define the enduring objective for ${input.projectName}.`);
91
132
  writeText(resolve(projectRoot, "src/content/people", `${stewardSlug}.mdx`), `---
92
133
  name: Launch Steward
93
134
  role: Team steward
@@ -99,17 +140,34 @@ tags:
99
140
  ---
100
141
 
101
142
  The launch steward keeps the first operating cycle legible while the hub moves from setup into active use.
143
+ `);
144
+ writeText(resolve(projectRoot, "src/content/objectives", "core.md"), `${frontmatter({
145
+ id: "objective:core",
146
+ title: `${input.projectName} Core Objective`,
147
+ description: coreObjectiveSummary,
148
+ date: today,
149
+ summary: coreObjectiveSummary,
150
+ status: "live",
151
+ timeHorizon: "strategic",
152
+ motivation: "The core objective anchors TreeSeed agent context and keeps project work aligned.",
153
+ primaryContributor: stewardSlug,
154
+ canonical: true
155
+ })}
156
+
157
+ ${coreObjective}
102
158
  `);
103
159
  writeText(resolve(projectRoot, "src/content/objectives", "launch-knowledge-hub.mdx"), `---
104
160
  id: ${objectiveId}
105
161
  title: Launch ${input.projectName}
106
162
  description: Bring the initial knowledge hub online with live managed infrastructure and a clear operating direction.
107
- date: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
163
+ date: ${today}
108
164
  summary: Stand up the hub, connect the runtime, and make the first workstream visible to the team.
109
165
  status: live
110
166
  timeHorizon: near-term
111
167
  motivation: TreeSeed launches should create immediately usable hubs instead of leaving teams in setup limbo.
112
168
  primaryContributor: ${stewardSlug}
169
+ relatedObjectives:
170
+ - core
113
171
  ---
114
172
 
115
173
  Launch ${input.projectName} as a living knowledge hub with real GitHub, Cloudflare, and Railway infrastructure.
@@ -118,7 +176,7 @@ Launch ${input.projectName} as a living knowledge hub with real GitHub, Cloudfla
118
176
  id: ${questionId}
119
177
  title: What Should The First Release Cover?
120
178
  description: Scope the first release around the foundation of the hub and the initial operating routines.
121
- date: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
179
+ date: ${today}
122
180
  summary: Define the first release around setup completion, clear direction, and baseline operating visibility.
123
181
  status: live
124
182
  questionType: strategy
@@ -133,7 +191,7 @@ The first release should verify that the hub is live, the core direction is visi
133
191
  writeText(resolve(projectRoot, "src/content/notes", `${noteSlug}.mdx`), `---
134
192
  title: ${input.projectName} Operating Model
135
193
  description: The initial working agreements for this Knowledge Hub.
136
- date: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
194
+ date: ${today}
137
195
  summary: Managed launch created the default branches, runtime wiring, and first operational checkpoints.
138
196
  status: live
139
197
  ---
@@ -144,7 +202,7 @@ This hub starts with a Knowledge Hub launch, a seeded objective, and a visible f
144
202
  id: ${proposalId}
145
203
  title: Establish The Initial Operating Routine
146
204
  description: Turn the seeded objective and question into a concrete launch proposal for the first team cycle.
147
- date: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
205
+ date: ${today}
148
206
  summary: Make the launch posture explicit so the team can move from setup into a concrete operating loop.
149
207
  status: live
150
208
  proposalType: strategy
@@ -165,7 +223,7 @@ Adopt a simple first operating routine: keep direction visible, keep the first r
165
223
  id: ${decisionId}
166
224
  title: Adopt The Initial Launch Posture
167
225
  description: Record the launch decision for the first operating cycle of the hub.
168
- date: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
226
+ date: ${today}
169
227
  summary: The Knowledge Hub launch will begin with a narrow first release and explicit direction artifacts.
170
228
  status: live
171
229
  decisionType: approved
@@ -237,13 +295,17 @@ console.log(\`Treeseed project API listening on \${server.url}\`);
237
295
  function applyManagedProjectDefaults(projectRoot, input) {
238
296
  const slug = slugify(input.projectSlug, "project");
239
297
  const marketBaseUrl = normalizeBaseUrl(input.marketBaseUrl ?? envOrNull("TREESEED_MARKET_API_BASE_URL") ?? "https://knowledge.coop");
240
- const siteUrl = resolveManagedWebUrl(slug);
298
+ const productionDomain = String(input.domains?.productionDomain ?? "").trim() || null;
299
+ const stagingDomain = String(input.domains?.stagingDomain ?? "").trim() || null;
300
+ const productionSiteUrl = domainUrl(productionDomain) ?? resolveManagedWebUrl(slug);
301
+ const stagingSiteUrl = domainUrl(stagingDomain);
302
+ const siteUrl = productionSiteUrl;
241
303
  const projectApiBaseUrl = normalizeBaseUrl(input.projectApiBaseUrl ?? resolveManagedApiUrl(slug));
242
304
  const cloudflareAccountId = envOrNull("CLOUDFLARE_ACCOUNT_ID") ?? "replace-with-cloudflare-account-id";
243
- const runtimeMode = input.hostingMode === "managed" ? "treeseed_managed" : input.hostingMode === "hybrid" ? "byo_attached" : "none";
244
- const runtimeRegistration = input.hostingMode === "managed" ? "required" : input.hostingMode === "hybrid" ? "optional" : "none";
305
+ const runtimeMode = input.hostingMode === "hybrid" ? "byo_attached" : "none";
306
+ const runtimeRegistration = input.hostingMode === "hybrid" ? "optional" : "none";
245
307
  const hubMode = input.hostingMode === "self_hosted" ? "customer_hosted" : "treeseed_hosted";
246
- const managedRuntime = runtimeMode === "treeseed_managed";
308
+ const managedRuntime = false;
247
309
  updateYamlFile(resolve(projectRoot, "treeseed.site.yaml"), (config) => ({
248
310
  ...config,
249
311
  name: input.projectName,
@@ -270,6 +332,7 @@ function applyManagedProjectDefaults(projectRoot, input) {
270
332
  cloudflare: {
271
333
  ...config.cloudflare ?? {},
272
334
  accountId: cloudflareAccountId,
335
+ ...input.domains?.zoneId ? { zoneId: input.domains.zoneId } : {},
273
336
  workerName: slug,
274
337
  queueName: `${slug}-agent-work`,
275
338
  dlqName: `${slug}-agent-work-dlq`,
@@ -297,35 +360,34 @@ function applyManagedProjectDefaults(projectRoot, input) {
297
360
  rootDir: ".",
298
361
  publicBaseUrl: siteUrl,
299
362
  localBaseUrl: "http://127.0.0.1:4321",
300
- ...config.surfaces?.web ?? {}
363
+ ...config.surfaces?.web ?? {},
364
+ environments: {
365
+ ...config.surfaces?.web?.environments ?? {},
366
+ ...stagingDomain ? { staging: { ...config.surfaces?.web?.environments?.staging ?? {}, domain: stagingDomain, baseUrl: stagingSiteUrl } } : {},
367
+ ...productionDomain ? { prod: { ...config.surfaces?.web?.environments?.prod ?? {}, domain: productionDomain, baseUrl: productionSiteUrl } } : {}
368
+ }
301
369
  },
302
370
  api: {
303
371
  enabled: managedRuntime,
304
- provider: managedRuntime ? "railway" : "none",
372
+ provider: "none",
305
373
  rootDir: ".",
306
374
  localBaseUrl: "http://127.0.0.1:3000",
307
375
  ...config.surfaces?.api ?? {}
308
376
  }
309
377
  },
310
378
  services: {
379
+ ...config.services ?? {},
311
380
  api: {
312
381
  enabled: managedRuntime,
313
- provider: managedRuntime ? "railway" : "none",
382
+ provider: "none",
314
383
  rootDir: ".",
315
384
  publicBaseUrl: projectApiBaseUrl,
316
- railway: {
317
- serviceName: `${slug}-api`,
318
- buildCommand: "npm run build:api",
319
- startCommand: "node ./src/api/server.js",
320
- healthcheckTimeoutSeconds: 120
321
- },
322
385
  environments: {
323
386
  local: {
324
387
  baseUrl: "http://127.0.0.1:3000"
325
388
  }
326
389
  }
327
- },
328
- ...config.services ?? {}
390
+ }
329
391
  },
330
392
  plugins: [{ package: "@treeseed/core/plugin-default" }],
331
393
  providers: {
@@ -419,7 +481,19 @@ function createDefaultWorkstream(projectId, input, seed) {
419
481
  }
420
482
  function pushDefaultWorkstreamBranch(projectRoot) {
421
483
  runGit(projectRoot, ["checkout", "-B", "task/initial-launch"], false);
422
- runGit(projectRoot, ["push", "-u", "origin", "task/initial-launch"], false);
484
+ runGit(projectRoot, ["push", "--force", "-u", "origin", "task/initial-launch"], false);
485
+ runGit(projectRoot, ["checkout", "main"], false);
486
+ }
487
+ function commitAndPushLaunchRepository(projectRoot, message, { forcePush = false } = {}) {
488
+ runGit(projectRoot, ["checkout", "main"], false);
489
+ runGit(projectRoot, ["add", "-A"], false);
490
+ if (gitOutput(projectRoot, ["status", "--porcelain"])) {
491
+ runGit(projectRoot, ["commit", "-m", message], false);
492
+ }
493
+ runGit(projectRoot, ["push", ...forcePush ? ["--force"] : [], "-u", "origin", "main"], false);
494
+ runGit(projectRoot, ["checkout", "staging"], false);
495
+ runGit(projectRoot, ["merge", "--ff-only", "main"], false);
496
+ runGit(projectRoot, ["push", ...forcePush ? ["--force"] : [], "-u", "origin", "staging"], false);
423
497
  runGit(projectRoot, ["checkout", "main"], false);
424
498
  }
425
499
  function loadProjectMetadata(projectId, input, seed, workstream, siteUrl, projectApiBaseUrl, repository) {
@@ -567,7 +641,7 @@ function scaffoldLaunchSource(projectRoot, input) {
567
641
  });
568
642
  }
569
643
  function repositoryHostGitHubEnvOverlay() {
570
- const token = process.env.TREESEED_HOSTED_HUBS_GITHUB_TOKEN || "";
644
+ const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN || process.env.TREESEED_HOSTED_HUBS_GITHUB_TOKEN || "";
571
645
  return token ? { ...process.env, GH_TOKEN: token, GITHUB_TOKEN: token } : process.env;
572
646
  }
573
647
  function prepareKnowledgeHubContentRepositoryRoot(sourceRoot, contentRoot, input) {
@@ -597,6 +671,8 @@ Content source for the ${input.projectName} TreeSeed Knowledge Hub.
597
671
  }
598
672
  function stripSoftwareContentOverlay(sourceRoot, input) {
599
673
  const contentRoot = resolve(sourceRoot, "src", "content");
674
+ const coreObjectiveSource = resolve(contentRoot, "objectives", "core.md");
675
+ const coreObjective = existsSync(coreObjectiveSource) ? readFileSync(coreObjectiveSource, "utf8") : null;
600
676
  rmSync(contentRoot, { recursive: true, force: true });
601
677
  mkdirSync(contentRoot, { recursive: true });
602
678
  writeFileSync(resolve(contentRoot, ".gitkeep"), "", "utf8");
@@ -611,6 +687,10 @@ Content source: ${input.contentRepository?.name ?? `${slugify(input.projectSlug,
611
687
  `,
612
688
  "utf8"
613
689
  );
690
+ if (coreObjective) {
691
+ mkdirSync(resolve(contentRoot, "objectives"), { recursive: true });
692
+ writeFileSync(resolve(contentRoot, "objectives", "core.md"), coreObjective, "utf8");
693
+ }
614
694
  }
615
695
  async function validateKnowledgeHubProviderLaunchPrerequisites(tenantRoot = process.cwd(), { valuesOverlay = {} } = {}) {
616
696
  const values = collectTreeseedConfigSeedValues(tenantRoot, "prod", process.env, valuesOverlay);
@@ -669,11 +749,13 @@ async function executeKnowledgeHubProviderLaunch(input, options = {}) {
669
749
  try {
670
750
  await appendPhase(phases, "repo_provision", "running", "Creating or connecting GitHub software repository.", reportPhase);
671
751
  const repository = input.existingRepository?.url ? {
752
+ ...resolveGitHubRemoteUrls(input.existingRepository.owner, input.existingRepository.name),
672
753
  slug: `${input.existingRepository.owner}/${input.existingRepository.name}`,
673
754
  owner: input.existingRepository.owner,
674
755
  name: input.existingRepository.name,
675
756
  url: input.existingRepository.url,
676
- visibility: input.existingRepository.visibility ?? input.repoVisibility ?? "private"
757
+ visibility: input.existingRepository.visibility ?? input.repoVisibility ?? "private",
758
+ defaultBranch: input.existingRepository.defaultBranch ?? "main"
677
759
  } : await createGitHubRepository({
678
760
  owner: repoOwner,
679
761
  name: repoName,
@@ -698,11 +780,13 @@ async function executeKnowledgeHubProviderLaunch(input, options = {}) {
698
780
  contentRepositoryWorkingRoot = mkdtempSync(join(tmpdir(), `market-content-${slugify(input.projectSlug, "project")}-`));
699
781
  prepareKnowledgeHubContentRepositoryRoot(workingRoot, contentRepositoryWorkingRoot, input);
700
782
  const createdContentRepository = input.contentRepository.url ? {
783
+ ...resolveGitHubRemoteUrls(input.contentRepository.owner ?? repoOwner, input.contentRepository.name),
701
784
  slug: `${slugify(input.contentRepository.owner ?? repoOwner, "treeseed-ai")}/${slugify(input.contentRepository.name, `${repoName}-content`)}`,
702
785
  owner: slugify(input.contentRepository.owner ?? repoOwner, "treeseed-ai"),
703
786
  name: slugify(input.contentRepository.name, `${repoName}-content`),
704
787
  url: input.contentRepository.url,
705
- visibility: input.contentRepository.visibility ?? input.repoVisibility ?? "private"
788
+ visibility: input.contentRepository.visibility ?? input.repoVisibility ?? "private",
789
+ defaultBranch: input.contentRepository.defaultBranch ?? "main"
706
790
  } : await createGitHubRepository({
707
791
  owner: slugify(input.contentRepository.owner ?? repoOwner, "treeseed-ai"),
708
792
  name: slugify(input.contentRepository.name, `${repoName}-content`),
@@ -714,7 +798,8 @@ async function executeKnowledgeHubProviderLaunch(input, options = {}) {
714
798
  const contentInitResult = initializeGitHubRepositoryWorkingTree(contentRepositoryWorkingRoot, createdContentRepository, {
715
799
  defaultBranch: input.contentRepository.defaultBranch ?? "main",
716
800
  createStaging: true,
717
- commitMessage: `Initialize ${input.projectName} content`
801
+ commitMessage: `Initialize ${input.projectName} content`,
802
+ forcePush: !input.contentRepository.url
718
803
  });
719
804
  contentRepository = {
720
805
  slug: createdContentRepository.slug,
@@ -732,22 +817,14 @@ async function executeKnowledgeHubProviderLaunch(input, options = {}) {
732
817
  const initResult = initializeGitHubRepositoryWorkingTree(workingRoot, repository, {
733
818
  defaultBranch: "main",
734
819
  createStaging: true,
735
- commitMessage: `Initialize ${input.projectName}`
820
+ commitMessage: `Initialize ${input.projectName}`,
821
+ push: false
736
822
  });
737
- pushDefaultWorkstreamBranch(workingRoot);
738
823
  const workflows = await ensureGitHubDeployAutomation(workingRoot, { valuesOverlay: prodEnvOverlay });
739
- const githubEnvironmentSync = [];
740
- for (const [scope, valuesOverlay] of [["staging", stagingEnvOverlay], ["prod", prodEnvOverlay]]) {
741
- githubEnvironmentSync.push(await syncTreeseedGitHubEnvironment({
742
- tenantRoot: workingRoot,
743
- scope,
744
- repository: repository.slug,
745
- valuesOverlay,
746
- execution: "sequential"
747
- }));
748
- }
749
- const workflowSummary = { ...workflows, environmentSync: githubEnvironmentSync };
750
- await appendPhase(phases, "workflow_bootstrap", "completed", "Configured GitHub workflows, secrets, and variables.", reportPhase);
824
+ commitAndPushLaunchRepository(workingRoot, `Configure ${input.projectName} deployment`, { forcePush: !input.existingRepository?.url });
825
+ pushDefaultWorkstreamBranch(workingRoot);
826
+ let workflowSummary = { ...workflows, environmentSync: [] };
827
+ await appendPhase(phases, "workflow_bootstrap", "completed", "Configured GitHub workflows.", reportPhase);
751
828
  await appendPhase(phases, "hosting_registration", "running", "Provisioning Cloudflare resources and deploy state.", reportPhase);
752
829
  const staging = await reconcileTreeseedTarget({
753
830
  tenantRoot: workingRoot,
@@ -759,6 +836,29 @@ async function executeKnowledgeHubProviderLaunch(input, options = {}) {
759
836
  target: createPersistentDeployTarget("prod"),
760
837
  env: { ...process.env, ...prodEnvOverlay }
761
838
  });
839
+ const turnstileOverlay = (state, base) => {
840
+ const widget = state?.turnstileWidgets?.formGuard ?? {};
841
+ return {
842
+ ...base,
843
+ ...typeof widget.sitekey === "string" && widget.sitekey.length > 0 ? { TREESEED_PUBLIC_TURNSTILE_SITE_KEY: widget.sitekey } : {},
844
+ ...typeof widget.secret === "string" && widget.secret.length > 0 ? { TREESEED_TURNSTILE_SECRET_KEY: widget.secret } : {}
845
+ };
846
+ };
847
+ const githubEnvironmentSync = [];
848
+ for (const [scope, valuesOverlay] of [
849
+ ["staging", turnstileOverlay(staging.state, stagingEnvOverlay)],
850
+ ["prod", turnstileOverlay(prod.state, prodEnvOverlay)]
851
+ ]) {
852
+ githubEnvironmentSync.push(await syncTreeseedGitHubEnvironment({
853
+ tenantRoot: workingRoot,
854
+ scope,
855
+ repository: repository.slug,
856
+ valuesOverlay,
857
+ execution: "sequential"
858
+ }));
859
+ }
860
+ workflowSummary = { ...workflowSummary, environmentSync: githubEnvironmentSync };
861
+ runRemoteD1Migrations(workingRoot, { scope: "staging" });
762
862
  runRemoteD1Migrations(workingRoot, { scope: "prod" });
763
863
  const verification = await collectTreeseedReconcileStatus({
764
864
  tenantRoot: workingRoot,
@@ -7,7 +7,7 @@ export declare function filterManagedHostGitHubEnvironment(required: {
7
7
  secrets: string[];
8
8
  variables: string[];
9
9
  }): {
10
- secrets: never[];
10
+ secrets: string[];
11
11
  variables: string[];
12
12
  };
13
13
  export declare function shouldExposeManagedHostRuntimeSecret(deployConfig: Pick<TreeseedDeployConfig, 'hosting' | 'hub' | 'runtime'> | null | undefined, _secretName: string, env?: Record<string, string | undefined>): boolean;
@@ -8,6 +8,9 @@ const SAFE_MANAGED_HOST_CI_VARIABLES = /* @__PURE__ */ new Set([
8
8
  "TREESEED_MARKET_API_BASE_URL",
9
9
  "TREESEED_PROJECT_ID"
10
10
  ]);
11
+ const SAFE_MANAGED_HOST_CI_SECRETS = /* @__PURE__ */ new Set([
12
+ "TREESEED_TURNSTILE_SECRET_KEY"
13
+ ]);
11
14
  const MANAGED_HOST_FORBIDDEN_VARIABLE_PREFIXES = [
12
15
  "CLOUDFLARE_",
13
16
  "RAILWAY_",
@@ -31,7 +34,7 @@ function usesManagedHostOperationRequests(deployConfig, env = process.env) {
31
34
  }
32
35
  function filterManagedHostGitHubEnvironment(required) {
33
36
  return {
34
- secrets: [],
37
+ secrets: required.secrets.filter((name) => SAFE_MANAGED_HOST_CI_SECRETS.has(name)),
35
38
  variables: required.variables.filter((name) => {
36
39
  if (SAFE_MANAGED_HOST_CI_VARIABLES.has(name)) {
37
40
  return true;
@@ -1087,6 +1087,22 @@ async function provisionProjectPlatform(options) {
1087
1087
  locator: state.lastDeployedUrl ?? null,
1088
1088
  metadata: { workerName: state.workerName }
1089
1089
  },
1090
+ {
1091
+ environment: options.scope,
1092
+ provider: "cloudflare",
1093
+ resourceKind: "kv",
1094
+ logicalName: state.kvNamespaces?.FORM_GUARD_KV?.name ?? "form-guard",
1095
+ locator: state.kvNamespaces?.FORM_GUARD_KV?.id ?? null,
1096
+ metadata: state.kvNamespaces?.FORM_GUARD_KV ?? {}
1097
+ },
1098
+ {
1099
+ environment: options.scope,
1100
+ provider: "cloudflare",
1101
+ resourceKind: "turnstile-widget",
1102
+ logicalName: state.turnstileWidgets?.formGuard?.name ?? "form-guard-turnstile",
1103
+ locator: state.turnstileWidgets?.formGuard?.sitekey ?? null,
1104
+ metadata: state.turnstileWidgets?.formGuard ?? {}
1105
+ },
1090
1106
  {
1091
1107
  environment: options.scope,
1092
1108
  provider: "cloudflare",
@@ -1,3 +1,4 @@
1
+ import { request as httpsRequest } from "node:https";
1
2
  const DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
2
3
  const DEFAULT_RAILWAY_WORKSPACE = "knowledge-coop";
3
4
  const RAILWAY_POSTGRES_TEMPLATE_ID = "b55da7dc-09be-4140-bc65-1284d15d349c";
@@ -295,7 +296,7 @@ async function railwayGraphqlRequest({
295
296
  const controller = new AbortController();
296
297
  let timer = null;
297
298
  try {
298
- const response = await Promise.race([
299
+ const response = fetchImpl === fetch ? await railwayGraphqlHttpsRequest(apiUrl || resolveRailwayApiUrl(env), token, { query, variables }, timeoutMs) : await Promise.race([
299
300
  fetchImpl(apiUrl || resolveRailwayApiUrl(env), {
300
301
  method: "POST",
301
302
  headers: {
@@ -304,7 +305,12 @@ async function railwayGraphqlRequest({
304
305
  },
305
306
  body: JSON.stringify({ query, variables }),
306
307
  signal: controller.signal
307
- }),
308
+ }).then(async (fetchResponse) => ({
309
+ ok: fetchResponse.ok,
310
+ status: fetchResponse.status,
311
+ payload: await fetchResponse.json().catch(() => ({})),
312
+ retryAfter: fetchResponse.headers.get("retry-after")
313
+ })),
308
314
  new Promise((_, reject) => {
309
315
  timer = setTimeout(() => {
310
316
  controller.abort();
@@ -312,11 +318,11 @@ async function railwayGraphqlRequest({
312
318
  }, timeoutMs);
313
319
  })
314
320
  ]);
315
- const payload = await response.json().catch(() => ({}));
321
+ const payload = response.payload;
316
322
  if (!response.ok || Array.isArray(payload.errors) && payload.errors.length > 0) {
317
323
  const message = normalizeRailwayErrorMessage(payload, response.status);
318
324
  const hasGraphqlErrors = Array.isArray(payload.errors) && payload.errors.length > 0;
319
- const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
325
+ const retryAfterMs = parseRetryAfterMs(response.retryAfter);
320
326
  const shouldRetry = isRetryableRailwayStatus(response.status) || /rate limit|too many requests/iu.test(message);
321
327
  const error = new Error(message);
322
328
  if (shouldRetry || hasGraphqlErrors && /rate limit|too many requests/iu.test(message)) {
@@ -340,6 +346,47 @@ async function railwayGraphqlRequest({
340
346
  }
341
347
  }
342
348
  }
349
+ async function railwayGraphqlHttpsRequest(url, token, body, timeoutMs) {
350
+ const rawBody = JSON.stringify(body);
351
+ return new Promise((resolve, reject) => {
352
+ const req = httpsRequest(url, {
353
+ method: "POST",
354
+ headers: {
355
+ authorization: `Bearer ${token}`,
356
+ "content-type": "application/json",
357
+ "content-length": Buffer.byteLength(rawBody)
358
+ },
359
+ timeout: timeoutMs
360
+ }, (res) => {
361
+ const chunks = [];
362
+ res.setEncoding("utf8");
363
+ res.on("data", (chunk) => chunks.push(chunk));
364
+ res.on("end", () => {
365
+ const text = chunks.join("");
366
+ let payload = {};
367
+ try {
368
+ payload = text ? JSON.parse(text) : {};
369
+ } catch {
370
+ payload = {};
371
+ }
372
+ const status = res.statusCode ?? 0;
373
+ const retryAfterHeader = res.headers["retry-after"];
374
+ resolve({
375
+ ok: status >= 200 && status < 300,
376
+ status,
377
+ payload,
378
+ retryAfter: Array.isArray(retryAfterHeader) ? retryAfterHeader[0] ?? null : retryAfterHeader ?? null
379
+ });
380
+ });
381
+ });
382
+ req.on("timeout", () => {
383
+ req.destroy(markRailwayTransientError(new Error(`Railway API request timed out after ${timeoutMs}ms.`)));
384
+ });
385
+ req.on("error", reject);
386
+ req.write(rawBody);
387
+ req.end();
388
+ });
389
+ }
343
390
  async function getRailwayAuthProfile({
344
391
  env = process.env,
345
392
  fetchImpl = fetch
@@ -839,13 +886,13 @@ async function ensureRailwayServiceInstanceConfiguration({
839
886
  runtimeMode,
840
887
  env = process.env,
841
888
  fetchImpl = fetch,
842
- settleAttempts = 24,
889
+ settleAttempts = 60,
843
890
  settleDelayMs = 5e3
844
891
  }) {
845
892
  let current = await getRailwayServiceInstance({ serviceId, environmentId, env, fetchImpl });
846
893
  if (!current.id) {
847
- for (let attempt = 0; attempt < 8 && !current.id; attempt += 1) {
848
- await new Promise((resolve) => setTimeout(resolve, 1500));
894
+ for (let attempt = 0; attempt < settleAttempts && !current.id; attempt += 1) {
895
+ await new Promise((resolve) => setTimeout(resolve, settleDelayMs));
849
896
  current = await getRailwayServiceInstance({ serviceId, environmentId, env, fetchImpl });
850
897
  }
851
898
  }
@@ -1248,7 +1295,8 @@ async function ensureRailwayServiceVolume({
1248
1295
  if (!volume) {
1249
1296
  volume = await createReplacementVolume();
1250
1297
  }
1251
- if (volume.name && volume.name !== name) {
1298
+ const desiredNameInUse = volumes.some((candidate) => candidate.id !== volume?.id && candidate.name === name);
1299
+ if (volume.name && volume.name !== name && !desiredNameInUse) {
1252
1300
  try {
1253
1301
  volume = await updateRailwayVolumeName({ volumeId: volume.id, name, env, fetchImpl }) ?? { ...volume, name };
1254
1302
  } catch (error) {
@@ -6,9 +6,10 @@ export declare function deriveRailwayMarketOperationsRunnerVolumeName(serviceNam
6
6
  export declare function railwayServiceRuntimeStartCommand(service: any): any;
7
7
  export declare function parseRailwayJsonOutput(output: any): any;
8
8
  export declare function collectRailwayDeploymentStatusChecks(statusPayload: any, scope: any, services: any): any;
9
- export declare function listRailwayServiceVolumesWithCli({ cwd, serviceId, environmentId, name, mountPath, env, }: {
9
+ export declare function listRailwayServiceVolumesWithCli({ cwd, serviceId, serviceName, environmentId, name, mountPath, env, }: {
10
10
  cwd: any;
11
11
  serviceId: any;
12
+ serviceName: any;
12
13
  environmentId: any;
13
14
  name: any;
14
15
  mountPath: any;