@treeseed/sdk 0.10.24 → 0.10.25

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 (39) hide show
  1. package/dist/index.d.ts +12 -2
  2. package/dist/index.js +42 -1
  3. package/dist/market-client.d.ts +23 -0
  4. package/dist/market-client.js +30 -0
  5. package/dist/operations/providers/default.js +103 -10
  6. package/dist/operations/repository-operations.d.ts +6 -1
  7. package/dist/operations/repository-operations.js +44 -0
  8. package/dist/operations/services/config-runtime.d.ts +24 -9
  9. package/dist/operations/services/config-runtime.js +60 -12
  10. package/dist/operations/services/deploy.js +6 -1
  11. package/dist/operations/services/hub-launch.js +1 -0
  12. package/dist/operations/services/hub-provider-launch.d.ts +11 -1
  13. package/dist/operations/services/hub-provider-launch.js +81 -8
  14. package/dist/operations/services/project-host-operations.d.ts +153 -0
  15. package/dist/operations/services/project-host-operations.js +365 -0
  16. package/dist/operations/services/project-platform.d.ts +198 -193
  17. package/dist/operations/services/project-platform.js +29 -14
  18. package/dist/operations/services/railway-deploy.d.ts +3 -0
  19. package/dist/operations/services/railway-deploy.js +74 -35
  20. package/dist/operations/services/release-candidate.js +8 -2
  21. package/dist/operations/services/template-host-bindings.d.ts +68 -0
  22. package/dist/operations/services/template-host-bindings.js +400 -0
  23. package/dist/operations/services/template-registry.d.ts +22 -2
  24. package/dist/operations/services/template-registry.js +60 -3
  25. package/dist/operations/services/template-secret-sync.d.ts +97 -0
  26. package/dist/operations/services/template-secret-sync.js +292 -0
  27. package/dist/platform/environment.d.ts +3 -0
  28. package/dist/project-workflow.d.ts +7 -1
  29. package/dist/scripts/scaffold-site.js +3 -2
  30. package/dist/scripts/test-scaffold.js +2 -1
  31. package/dist/sdk-types.d.ts +87 -0
  32. package/dist/sdk-types.js +29 -0
  33. package/dist/template-catalog.js +3 -1
  34. package/dist/template-launch-requirements.d.ts +118 -0
  35. package/dist/template-launch-requirements.js +759 -0
  36. package/dist/template-launch-ui.d.ts +85 -0
  37. package/dist/template-launch-ui.js +189 -0
  38. package/dist/treeseed/template-catalog/catalog.fixture.json +330 -3
  39. package/package.json +13 -1
@@ -0,0 +1,85 @@
1
+ import type { ProjectEnvironmentName, TemplateLaunchRequirements, TemplateSecretTarget } from './sdk-types.ts';
2
+ import type { ProjectLaunchHostInventoryRecord } from './template-launch-requirements.ts';
3
+ export interface ProjectLaunchRequirementHostChoice {
4
+ value: string;
5
+ label: string;
6
+ mode: 'none' | 'team_owned' | 'treeseed_managed' | 'new';
7
+ hostId?: string | null;
8
+ managedHostKey?: string | null;
9
+ provider: string;
10
+ type: string;
11
+ selected: boolean;
12
+ readiness: 'ready' | 'configuration_required' | 'inactive' | 'new' | 'none';
13
+ rootDomain?: string | null;
14
+ }
15
+ export interface ProjectLaunchHostRequirementViewModel {
16
+ kind: 'host';
17
+ key: string;
18
+ type: string;
19
+ displayName: string;
20
+ purpose: string;
21
+ required: boolean;
22
+ compatibleProviders: string[];
23
+ defaultSelection?: 'team-default' | 'managed' | 'none';
24
+ environmentScopes: ProjectEnvironmentName[];
25
+ configWritePreviews: Array<{
26
+ target: string;
27
+ path: string;
28
+ valueFrom: string;
29
+ writeWhen?: string;
30
+ }>;
31
+ environmentWritePreviews: Array<{
32
+ env: string;
33
+ targets: string[];
34
+ scopes: ProjectEnvironmentName[];
35
+ sensitivity: string;
36
+ }>;
37
+ choices: ProjectLaunchRequirementHostChoice[];
38
+ }
39
+ export interface ProjectLaunchResourceRequirementViewModel {
40
+ kind: 'resource';
41
+ key: string;
42
+ type: string;
43
+ displayName: string;
44
+ purpose: string;
45
+ required: boolean;
46
+ compatibleProviders: string[];
47
+ configWritePreviews: Array<{
48
+ target: string;
49
+ path: string;
50
+ valueFrom: string;
51
+ writeWhen?: string;
52
+ }>;
53
+ environmentWritePreviews: Array<{
54
+ env: string;
55
+ targets: string[];
56
+ scopes: ProjectEnvironmentName[];
57
+ sensitivity: string;
58
+ }>;
59
+ status: 'unsupported_in_standard_launch';
60
+ }
61
+ export interface ProjectLaunchSecretRequirementViewModel {
62
+ kind: 'secret';
63
+ key: string;
64
+ env: string;
65
+ required: boolean;
66
+ sensitivity: string;
67
+ source: string;
68
+ targets: TemplateSecretTarget[];
69
+ status: 'planned';
70
+ }
71
+ export interface ProjectLaunchRequirementsViewModel {
72
+ version?: number;
73
+ hosts: ProjectLaunchHostRequirementViewModel[];
74
+ resources: ProjectLaunchResourceRequirementViewModel[];
75
+ secrets: ProjectLaunchSecretRequirementViewModel[];
76
+ }
77
+ export interface DeriveProjectLaunchRequirementsViewModelOptions {
78
+ launchRequirements?: TemplateLaunchRequirements | null;
79
+ repositoryHosts?: ProjectLaunchHostInventoryRecord[];
80
+ teamHosts?: ProjectLaunchHostInventoryRecord[];
81
+ managedHosts?: ProjectLaunchHostInventoryRecord[];
82
+ defaultHosts?: Record<string, unknown> | null;
83
+ environmentScopes?: ProjectEnvironmentName[];
84
+ }
85
+ export declare function deriveProjectLaunchRequirementsViewModel(options: DeriveProjectLaunchRequirementsViewModelOptions): ProjectLaunchRequirementsViewModel;
@@ -0,0 +1,189 @@
1
+ function hostType(host) {
2
+ const metadataType = typeof host.metadata?.hostType === "string" ? host.metadata.hostType : host.type;
3
+ if (metadataType === "repository_host" || metadataType === "repository") return "repository";
4
+ if (metadataType === "web_host" || metadataType === "cloudflare" || metadataType === "web") return "web";
5
+ if (metadataType === "email_host" || metadataType === "smtp" || metadataType === "email") return "email";
6
+ if (metadataType === "ai_host" || metadataType === "ai") return "ai";
7
+ if (host.provider === "github") return "repository";
8
+ if (host.provider === "cloudflare") return "web";
9
+ if (host.provider === "smtp") return "email";
10
+ return metadataType ?? "";
11
+ }
12
+ function rootDomain(host) {
13
+ const metadata = host.metadata ?? {};
14
+ const dns = metadata.dns && typeof metadata.dns === "object" ? metadata.dns : {};
15
+ const value = dns.zoneName ?? dns.rootDomain ?? metadata.rootDomain ?? metadata.webRootDomain ?? metadata.cloudflareZoneName;
16
+ return typeof value === "string" && value.trim() ? value.trim() : null;
17
+ }
18
+ function hostLabel(host, fallback) {
19
+ const ownership = host.ownership === "treeseed_managed" ? "managed" : host.accountLabel ?? host.organizationOrOwner ?? host.provider ?? "team-owned";
20
+ return `${host.name || fallback} (${ownership})`;
21
+ }
22
+ function compatibleHosts(requirement, options) {
23
+ const source = requirement.type === "repository" ? options.repositoryHosts ?? [] : [...options.teamHosts ?? [], ...options.managedHosts ?? []];
24
+ return source.filter((host) => {
25
+ if (hostType(host) !== requirement.type) return false;
26
+ return !requirement.compatibleProviders?.length || requirement.compatibleProviders.includes(host.provider);
27
+ });
28
+ }
29
+ function defaultHostId(requirement, defaultHosts) {
30
+ for (const key of [requirement.key, requirement.type, requirement.key === "sourceRepository" ? "repository" : null, requirement.key === "publicWeb" ? "web" : null, requirement.key === "transactionalEmail" ? "email" : null]) {
31
+ if (!key) continue;
32
+ const value = defaultHosts?.[key];
33
+ if (typeof value === "string" && value.trim()) return value.trim();
34
+ }
35
+ return null;
36
+ }
37
+ function selectedChoiceIndex(choices, requirement, defaultHosts) {
38
+ const configuredDefault = defaultHostId(requirement, defaultHosts);
39
+ if (configuredDefault) {
40
+ const index = choices.findIndex((choice) => choice.hostId === configuredDefault || choice.managedHostKey === configuredDefault);
41
+ if (index >= 0) return index;
42
+ }
43
+ if (requirement.defaultSelection === "team-default") {
44
+ const teamIndex = choices.findIndex((choice) => choice.mode === "team_owned");
45
+ if (teamIndex >= 0) return teamIndex;
46
+ }
47
+ if (requirement.defaultSelection === "managed" || requirement.defaultSelection === "team-default") {
48
+ const managedIndex = choices.findIndex((choice) => choice.mode === "treeseed_managed");
49
+ if (managedIndex >= 0) return managedIndex;
50
+ }
51
+ if (!requirement.required) {
52
+ const noneIndex = choices.findIndex((choice) => choice.mode === "none");
53
+ if (noneIndex >= 0) return noneIndex;
54
+ }
55
+ return choices.length > 0 ? 0 : -1;
56
+ }
57
+ function deriveHostRequirement(requirement, options) {
58
+ const scopes = options.environmentScopes ?? ["staging", "prod"];
59
+ const choices = compatibleHosts(requirement, options).map((host) => {
60
+ const managed = host.ownership === "treeseed_managed";
61
+ const status = typeof host.status === "string" && host.status ? host.status : "active";
62
+ return {
63
+ value: `${managed ? "treeseed_managed" : "team_owned"}:${host.id}`,
64
+ label: hostLabel(host, requirement.displayName),
65
+ mode: managed ? "treeseed_managed" : "team_owned",
66
+ hostId: managed && requirement.type !== "repository" ? null : host.id,
67
+ managedHostKey: managed ? host.id : null,
68
+ provider: host.provider,
69
+ type: requirement.type,
70
+ selected: false,
71
+ readiness: status === "active" || status === "ready" ? "ready" : status === "configuration_required" ? "configuration_required" : "inactive",
72
+ rootDomain: rootDomain(host)
73
+ };
74
+ });
75
+ if (requirement.type === "repository" && !choices.some((choice) => choice.value === "treeseed_managed:platform:github:hosted-hubs" || choice.value === "team_owned:platform:github:hosted-hubs")) {
76
+ choices.unshift({
77
+ value: "treeseed_managed:platform:github:hosted-hubs",
78
+ label: "TreeSeed hosted repositories (managed)",
79
+ mode: "treeseed_managed",
80
+ hostId: "platform:github:hosted-hubs",
81
+ managedHostKey: "platform:github:hosted-hubs",
82
+ provider: "github",
83
+ type: "repository",
84
+ selected: false,
85
+ readiness: "ready",
86
+ rootDomain: null
87
+ });
88
+ }
89
+ if (!requirement.required) {
90
+ choices.push({
91
+ value: `none:${requirement.key}`,
92
+ label: `No ${requirement.displayName.toLowerCase()}`,
93
+ mode: "none",
94
+ hostId: null,
95
+ managedHostKey: null,
96
+ provider: requirement.compatibleProviders?.[0] ?? "",
97
+ type: requirement.type,
98
+ selected: false,
99
+ readiness: "none",
100
+ rootDomain: null
101
+ });
102
+ }
103
+ choices.push({
104
+ value: `new:${requirement.type}`,
105
+ label: `Create new ${requirement.type} host...`,
106
+ mode: "new",
107
+ hostId: null,
108
+ managedHostKey: null,
109
+ provider: requirement.compatibleProviders?.[0] ?? "",
110
+ type: requirement.type,
111
+ selected: false,
112
+ readiness: "new",
113
+ rootDomain: null
114
+ });
115
+ const selectedIndex = selectedChoiceIndex(choices, requirement, options.defaultHosts);
116
+ if (selectedIndex >= 0) choices[selectedIndex] = { ...choices[selectedIndex], selected: true };
117
+ return {
118
+ kind: "host",
119
+ key: requirement.key,
120
+ type: requirement.type,
121
+ displayName: requirement.displayName,
122
+ purpose: requirement.purpose,
123
+ required: requirement.required,
124
+ compatibleProviders: requirement.compatibleProviders ?? [],
125
+ defaultSelection: requirement.defaultSelection,
126
+ environmentScopes: scopes,
127
+ configWritePreviews: (requirement.configWrites ?? []).map((write) => ({
128
+ target: write.target,
129
+ path: write.path,
130
+ valueFrom: write.valueFrom,
131
+ writeWhen: write.writeWhen
132
+ })),
133
+ environmentWritePreviews: (requirement.environmentWrites ?? []).map((write) => ({
134
+ env: write.env,
135
+ targets: write.targets ?? [],
136
+ scopes: write.scopes ?? scopes,
137
+ sensitivity: write.sensitivity ?? "plain"
138
+ })),
139
+ choices
140
+ };
141
+ }
142
+ function deriveResourceRequirement(requirement) {
143
+ return {
144
+ kind: "resource",
145
+ key: requirement.key,
146
+ type: requirement.type,
147
+ displayName: requirement.displayName,
148
+ purpose: requirement.purpose,
149
+ required: requirement.required,
150
+ compatibleProviders: requirement.compatibleProviders ?? [],
151
+ configWritePreviews: (requirement.configWrites ?? []).map((write) => ({
152
+ target: write.target,
153
+ path: write.path,
154
+ valueFrom: write.valueFrom,
155
+ writeWhen: write.writeWhen
156
+ })),
157
+ environmentWritePreviews: (requirement.environmentWrites ?? []).map((write) => ({
158
+ env: write.env,
159
+ targets: write.targets ?? [],
160
+ scopes: write.scopes ?? ["staging", "prod"],
161
+ sensitivity: write.sensitivity ?? "plain"
162
+ })),
163
+ status: "unsupported_in_standard_launch"
164
+ };
165
+ }
166
+ function deriveSecretRequirement(requirement) {
167
+ return {
168
+ kind: "secret",
169
+ key: requirement.key,
170
+ env: requirement.env,
171
+ required: requirement.required,
172
+ sensitivity: requirement.sensitivity,
173
+ source: requirement.source,
174
+ targets: requirement.targets,
175
+ status: "planned"
176
+ };
177
+ }
178
+ function deriveProjectLaunchRequirementsViewModel(options) {
179
+ const launchRequirements = options.launchRequirements ?? {};
180
+ return {
181
+ version: launchRequirements.version,
182
+ hosts: (launchRequirements.hosts ?? []).map((requirement) => deriveHostRequirement(requirement, options)),
183
+ resources: (launchRequirements.resources ?? []).map(deriveResourceRequirement),
184
+ secrets: (launchRequirements.secrets ?? []).map(deriveSecretRequirement)
185
+ };
186
+ }
187
+ export {
188
+ deriveProjectLaunchRequirementsViewModel
189
+ };
@@ -83,7 +83,7 @@
83
83
  "kind": "git",
84
84
  "repoUrl": "https://github.com/treeseed-ai/starter-research.git",
85
85
  "directory": ".",
86
- "ref": "main",
86
+ "ref": "staging",
87
87
  "integrity": "pending-external-repo"
88
88
  },
89
89
  "hooksPolicy": "builtin_only",
@@ -94,6 +94,64 @@
94
94
  "license": "AGPL-3.0-only",
95
95
  "support": "community"
96
96
  },
97
+ "launchRequirements": {
98
+ "version": 1,
99
+ "hosts": [
100
+ {
101
+ "kind": "host",
102
+ "key": "sourceRepository",
103
+ "type": "repository",
104
+ "required": true,
105
+ "compatibleProviders": ["github"],
106
+ "displayName": "Source repository",
107
+ "purpose": "Create and push the generated research project repository.",
108
+ "defaultSelection": "team-default",
109
+ "configWrites": [
110
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.sourceRepository.provider", "valueFrom": "selectedHost.provider" },
111
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.sourceRepository.owner", "valueFrom": "selectedHost.github.owner" },
112
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.sourceRepository.repository", "valueFrom": "derived.repositoryName" }
113
+ ],
114
+ "environmentWrites": [
115
+ { "env": "GITHUB_TOKEN", "valueFrom": "selectedHost.token", "targets": ["github-secret"], "scopes": ["staging", "prod"], "sensitivity": "secret" }
116
+ ]
117
+ },
118
+ {
119
+ "kind": "host",
120
+ "key": "publicWeb",
121
+ "type": "web",
122
+ "required": true,
123
+ "compatibleProviders": ["cloudflare"],
124
+ "displayName": "Public web host",
125
+ "purpose": "Deploy the research site, previews, content storage, and web runtime resources.",
126
+ "defaultSelection": "managed",
127
+ "configWrites": [
128
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.publicWeb.provider", "valueFrom": "selectedHost.provider" },
129
+ { "target": "treeseed.site.yaml", "path": "surfaces.web.provider", "valueFrom": "selectedHost.provider" },
130
+ { "target": "treeseed.site.yaml", "path": "surfaces.web.environments.prod.domain", "valueFrom": "launchInput.domains.productionDomain", "writeWhen": "host-selected" },
131
+ { "target": "treeseed.site.yaml", "path": "surfaces.web.environments.staging.domain", "valueFrom": "launchInput.domains.stagingDomain", "writeWhen": "host-selected" }
132
+ ],
133
+ "environmentWrites": [
134
+ { "env": "TREESEED_PUBLIC_WEB_PROVIDER", "valueFrom": "selectedHost.provider", "targets": ["github-variable", "cloudflare-var"], "scopes": ["staging", "prod"], "sensitivity": "plain" }
135
+ ]
136
+ },
137
+ {
138
+ "kind": "host",
139
+ "key": "transactionalEmail",
140
+ "type": "email",
141
+ "required": false,
142
+ "compatibleProviders": ["smtp"],
143
+ "displayName": "Transactional email",
144
+ "purpose": "Send form, account, and research project notification email.",
145
+ "defaultSelection": "managed",
146
+ "configWrites": [
147
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.transactionalEmail.provider", "valueFrom": "selectedHost.provider", "writeWhen": "host-selected" }
148
+ ],
149
+ "environmentWrites": [
150
+ { "env": "SMTP_HOST", "valueFrom": "selectedHost.smtpHost", "targets": ["github-secret", "railway-secret"], "scopes": ["staging", "prod"], "sensitivity": "secret" }
151
+ ]
152
+ }
153
+ ]
154
+ },
97
155
  "relatedBooks": [],
98
156
  "relatedKnowledge": [],
99
157
  "relatedObjectives": []
@@ -133,7 +191,7 @@
133
191
  "kind": "git",
134
192
  "repoUrl": "https://github.com/treeseed-ai/starter-engineering.git",
135
193
  "directory": ".",
136
- "ref": "main",
194
+ "ref": "staging",
137
195
  "integrity": "pending-external-repo"
138
196
  },
139
197
  "hooksPolicy": "builtin_only",
@@ -144,6 +202,64 @@
144
202
  "license": "AGPL-3.0-only",
145
203
  "support": "community"
146
204
  },
205
+ "launchRequirements": {
206
+ "version": 1,
207
+ "hosts": [
208
+ {
209
+ "kind": "host",
210
+ "key": "sourceRepository",
211
+ "type": "repository",
212
+ "required": true,
213
+ "compatibleProviders": ["github"],
214
+ "displayName": "Source repository",
215
+ "purpose": "Create and push the generated engineering project repository.",
216
+ "defaultSelection": "team-default",
217
+ "configWrites": [
218
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.sourceRepository.provider", "valueFrom": "selectedHost.provider" },
219
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.sourceRepository.owner", "valueFrom": "selectedHost.github.owner" },
220
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.sourceRepository.repository", "valueFrom": "derived.repositoryName" }
221
+ ],
222
+ "environmentWrites": [
223
+ { "env": "GITHUB_TOKEN", "valueFrom": "selectedHost.token", "targets": ["github-secret"], "scopes": ["staging", "prod"], "sensitivity": "secret" }
224
+ ]
225
+ },
226
+ {
227
+ "kind": "host",
228
+ "key": "publicWeb",
229
+ "type": "web",
230
+ "required": true,
231
+ "compatibleProviders": ["cloudflare"],
232
+ "displayName": "Public web host",
233
+ "purpose": "Deploy the engineering site, previews, content storage, and web runtime resources.",
234
+ "defaultSelection": "managed",
235
+ "configWrites": [
236
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.publicWeb.provider", "valueFrom": "selectedHost.provider" },
237
+ { "target": "treeseed.site.yaml", "path": "surfaces.web.provider", "valueFrom": "selectedHost.provider" },
238
+ { "target": "treeseed.site.yaml", "path": "surfaces.web.environments.prod.domain", "valueFrom": "launchInput.domains.productionDomain", "writeWhen": "host-selected" },
239
+ { "target": "treeseed.site.yaml", "path": "surfaces.web.environments.staging.domain", "valueFrom": "launchInput.domains.stagingDomain", "writeWhen": "host-selected" }
240
+ ],
241
+ "environmentWrites": [
242
+ { "env": "TREESEED_PUBLIC_WEB_PROVIDER", "valueFrom": "selectedHost.provider", "targets": ["github-variable", "cloudflare-var"], "scopes": ["staging", "prod"], "sensitivity": "plain" }
243
+ ]
244
+ },
245
+ {
246
+ "kind": "host",
247
+ "key": "transactionalEmail",
248
+ "type": "email",
249
+ "required": false,
250
+ "compatibleProviders": ["smtp"],
251
+ "displayName": "Transactional email",
252
+ "purpose": "Send form, account, and engineering project notification email.",
253
+ "defaultSelection": "managed",
254
+ "configWrites": [
255
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.transactionalEmail.provider", "valueFrom": "selectedHost.provider", "writeWhen": "host-selected" }
256
+ ],
257
+ "environmentWrites": [
258
+ { "env": "SMTP_HOST", "valueFrom": "selectedHost.smtpHost", "targets": ["github-secret", "railway-secret"], "scopes": ["staging", "prod"], "sensitivity": "secret" }
259
+ ]
260
+ }
261
+ ]
262
+ },
147
263
  "relatedBooks": [],
148
264
  "relatedKnowledge": [],
149
265
  "relatedObjectives": []
@@ -183,7 +299,7 @@
183
299
  "kind": "git",
184
300
  "repoUrl": "https://github.com/treeseed-ai/starter-information-hub.git",
185
301
  "directory": ".",
186
- "ref": "main",
302
+ "ref": "staging",
187
303
  "integrity": "pending-external-repo"
188
304
  },
189
305
  "hooksPolicy": "builtin_only",
@@ -194,6 +310,217 @@
194
310
  "license": "AGPL-3.0-only",
195
311
  "support": "community"
196
312
  },
313
+ "launchRequirements": {
314
+ "version": 1,
315
+ "hosts": [
316
+ {
317
+ "kind": "host",
318
+ "key": "sourceRepository",
319
+ "type": "repository",
320
+ "required": true,
321
+ "compatibleProviders": ["github"],
322
+ "displayName": "Source repository",
323
+ "purpose": "Create and push the generated information hub project repository.",
324
+ "defaultSelection": "team-default",
325
+ "configWrites": [
326
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.sourceRepository.provider", "valueFrom": "selectedHost.provider" },
327
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.sourceRepository.owner", "valueFrom": "selectedHost.github.owner" },
328
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.sourceRepository.repository", "valueFrom": "derived.repositoryName" }
329
+ ],
330
+ "environmentWrites": [
331
+ { "env": "GITHUB_TOKEN", "valueFrom": "selectedHost.token", "targets": ["github-secret"], "scopes": ["staging", "prod"], "sensitivity": "secret" }
332
+ ]
333
+ },
334
+ {
335
+ "kind": "host",
336
+ "key": "publicWeb",
337
+ "type": "web",
338
+ "required": true,
339
+ "compatibleProviders": ["cloudflare"],
340
+ "displayName": "Public web host",
341
+ "purpose": "Deploy the information hub site, previews, content storage, and web runtime resources.",
342
+ "defaultSelection": "managed",
343
+ "configWrites": [
344
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.publicWeb.provider", "valueFrom": "selectedHost.provider" },
345
+ { "target": "treeseed.site.yaml", "path": "surfaces.web.provider", "valueFrom": "selectedHost.provider" },
346
+ { "target": "treeseed.site.yaml", "path": "surfaces.web.environments.prod.domain", "valueFrom": "launchInput.domains.productionDomain", "writeWhen": "host-selected" },
347
+ { "target": "treeseed.site.yaml", "path": "surfaces.web.environments.staging.domain", "valueFrom": "launchInput.domains.stagingDomain", "writeWhen": "host-selected" }
348
+ ],
349
+ "environmentWrites": [
350
+ { "env": "TREESEED_PUBLIC_WEB_PROVIDER", "valueFrom": "selectedHost.provider", "targets": ["github-variable", "cloudflare-var"], "scopes": ["staging", "prod"], "sensitivity": "plain" }
351
+ ]
352
+ },
353
+ {
354
+ "kind": "host",
355
+ "key": "transactionalEmail",
356
+ "type": "email",
357
+ "required": false,
358
+ "compatibleProviders": ["smtp"],
359
+ "displayName": "Transactional email",
360
+ "purpose": "Send form, account, and information hub project notification email.",
361
+ "defaultSelection": "managed",
362
+ "configWrites": [
363
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.transactionalEmail.provider", "valueFrom": "selectedHost.provider", "writeWhen": "host-selected" }
364
+ ],
365
+ "environmentWrites": [
366
+ { "env": "SMTP_HOST", "valueFrom": "selectedHost.smtpHost", "targets": ["github-secret", "railway-secret"], "scopes": ["staging", "prod"], "sensitivity": "secret" }
367
+ ]
368
+ }
369
+ ]
370
+ },
371
+ "relatedBooks": [],
372
+ "relatedKnowledge": [],
373
+ "relatedObjectives": []
374
+ },
375
+ {
376
+ "id": "market-control-plane",
377
+ "displayName": "TreeSeed Market Control Plane",
378
+ "description": "Draft first-party template requirements for deploying the TreeSeed Market control plane with dynamic host and Railway resource selection.",
379
+ "summary": "A draft control-plane template that models the Market web/API, operations runner, and PostgreSQL resource roles without hardcoded provider accounts.",
380
+ "status": "draft",
381
+ "featured": false,
382
+ "category": "reference-app",
383
+ "audience": [
384
+ "operators",
385
+ "platform-teams"
386
+ ],
387
+ "tags": [
388
+ "market",
389
+ "control-plane",
390
+ "railway",
391
+ "postgres",
392
+ "operations-runner"
393
+ ],
394
+ "publisher": {
395
+ "id": "treeseed",
396
+ "name": "TreeSeed",
397
+ "url": "https://treeseed.ai"
398
+ },
399
+ "publisherVerified": true,
400
+ "templateVersion": "0.1.0",
401
+ "templateApiVersion": 1,
402
+ "minCliVersion": "0.1.1",
403
+ "minCoreVersion": "0.1.2",
404
+ "fulfillment": {
405
+ "mode": "git",
406
+ "source": {
407
+ "kind": "git",
408
+ "repoUrl": "https://github.com/treeseed-ai/market.git",
409
+ "directory": ".",
410
+ "ref": "main",
411
+ "integrity": "draft-requirements-only"
412
+ },
413
+ "hooksPolicy": "builtin_only",
414
+ "supportsReconcile": true
415
+ },
416
+ "offer": {
417
+ "priceModel": "free",
418
+ "license": "AGPL-3.0-only",
419
+ "support": "community"
420
+ },
421
+ "launchRequirements": {
422
+ "version": 1,
423
+ "hosts": [
424
+ {
425
+ "kind": "host",
426
+ "key": "sourceRepository",
427
+ "type": "repository",
428
+ "required": true,
429
+ "compatibleProviders": ["github"],
430
+ "displayName": "Source repository",
431
+ "purpose": "Create and push the Market control-plane repository.",
432
+ "defaultSelection": "team-default",
433
+ "configWrites": [
434
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.sourceRepository.provider", "valueFrom": "selectedHost.provider" },
435
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.sourceRepository.owner", "valueFrom": "selectedHost.github.owner" },
436
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.sourceRepository.repository", "valueFrom": "derived.repositoryName" }
437
+ ],
438
+ "environmentWrites": [
439
+ { "env": "GITHUB_TOKEN", "valueFrom": "selectedHost.token", "targets": ["github-secret"], "scopes": ["staging", "prod"], "sensitivity": "secret" }
440
+ ]
441
+ },
442
+ {
443
+ "kind": "host",
444
+ "key": "publicWeb",
445
+ "type": "web",
446
+ "required": true,
447
+ "compatibleProviders": ["cloudflare"],
448
+ "displayName": "Public web host",
449
+ "purpose": "Deploy the Market web surface and public control-plane routes.",
450
+ "defaultSelection": "managed",
451
+ "configWrites": [
452
+ { "target": "treeseed.site.yaml", "path": "hosting.hostBindings.publicWeb.provider", "valueFrom": "selectedHost.provider" },
453
+ { "target": "treeseed.site.yaml", "path": "hosting.kind", "valueFrom": "literal.market_control_plane" },
454
+ { "target": "treeseed.site.yaml", "path": "surfaces.web.provider", "valueFrom": "selectedHost.provider" },
455
+ { "target": "treeseed.site.yaml", "path": "surfaces.web.environments.prod.domain", "valueFrom": "launchInput.domains.productionDomain", "writeWhen": "host-selected" },
456
+ { "target": "treeseed.site.yaml", "path": "surfaces.web.environments.staging.domain", "valueFrom": "launchInput.domains.stagingDomain", "writeWhen": "host-selected" }
457
+ ],
458
+ "environmentWrites": [
459
+ { "env": "TREESEED_PUBLIC_WEB_PROVIDER", "valueFrom": "selectedHost.provider", "targets": ["github-variable", "cloudflare-var"], "scopes": ["staging", "prod"], "sensitivity": "plain" }
460
+ ]
461
+ }
462
+ ],
463
+ "resources": [
464
+ {
465
+ "kind": "resource",
466
+ "key": "marketDatabase",
467
+ "type": "database",
468
+ "required": true,
469
+ "compatibleProviders": ["railway-postgres"],
470
+ "displayName": "Market database",
471
+ "purpose": "Provide the PostgreSQL database used by the Market API control plane.",
472
+ "configWrites": [
473
+ { "target": "treeseed.site.yaml", "path": "services.marketDatabase.enabled", "valueFrom": "literal.true" },
474
+ { "target": "treeseed.site.yaml", "path": "services.marketDatabase.provider", "valueFrom": "literal.railway" },
475
+ { "target": "treeseed.site.yaml", "path": "services.marketDatabase.railway.resourceType", "valueFrom": "literal.postgres" },
476
+ { "target": "treeseed.site.yaml", "path": "services.marketDatabase.railway.serviceName", "valueFrom": "selectedResource.configValues.serviceName" },
477
+ { "target": "treeseed.site.yaml", "path": "services.marketDatabase.railway.environmentVariable", "valueFrom": "literal.TREESEED_MARKET_DATABASE_URL" }
478
+ ],
479
+ "environmentWrites": [
480
+ { "env": "TREESEED_MARKET_DATABASE_URL", "valueFrom": "selectedResource.secretRefs.databaseUrl", "targets": ["github-secret", "railway-secret"], "scopes": ["staging", "prod"], "sensitivity": "secret" }
481
+ ]
482
+ },
483
+ {
484
+ "kind": "resource",
485
+ "key": "api",
486
+ "type": "service",
487
+ "required": true,
488
+ "compatibleProviders": ["railway"],
489
+ "displayName": "Market API service",
490
+ "purpose": "Run the Market API service that serves the control-plane routes.",
491
+ "configWrites": [
492
+ { "target": "treeseed.site.yaml", "path": "services.api.enabled", "valueFrom": "literal.true" },
493
+ { "target": "treeseed.site.yaml", "path": "services.api.provider", "valueFrom": "literal.railway" },
494
+ { "target": "treeseed.site.yaml", "path": "services.api.railway.serviceName", "valueFrom": "selectedResource.configValues.serviceName" },
495
+ { "target": "treeseed.site.yaml", "path": "services.api.railway.rootDirectory", "valueFrom": "literal." },
496
+ { "target": "treeseed.site.yaml", "path": "services.api.railway.startCommand", "valueFrom": "literal.node dist/api/server.js" }
497
+ ],
498
+ "environmentWrites": [
499
+ { "env": "TREESEED_MARKET_DATABASE_URL", "valueFrom": "selectedResource.secretRefs.databaseUrl", "targets": ["railway-secret"], "scopes": ["staging", "prod"], "sensitivity": "secret" }
500
+ ]
501
+ },
502
+ {
503
+ "kind": "resource",
504
+ "key": "marketOperationsRunner",
505
+ "type": "service",
506
+ "required": true,
507
+ "compatibleProviders": ["railway"],
508
+ "displayName": "Market operations runner",
509
+ "purpose": "Run the governed Market operations runner for queued control-plane operations.",
510
+ "configWrites": [
511
+ { "target": "treeseed.site.yaml", "path": "services.marketOperationsRunner.enabled", "valueFrom": "literal.true" },
512
+ { "target": "treeseed.site.yaml", "path": "services.marketOperationsRunner.provider", "valueFrom": "literal.railway" },
513
+ { "target": "treeseed.site.yaml", "path": "services.marketOperationsRunner.railway.serviceName", "valueFrom": "selectedResource.configValues.serviceName" },
514
+ { "target": "treeseed.site.yaml", "path": "services.marketOperationsRunner.railway.startCommand", "valueFrom": "literal.node dist/market-operations-runner/runner.js" }
515
+ ],
516
+ "environmentWrites": [
517
+ { "env": "TREESEED_MARKET_DATABASE_URL", "valueFrom": "selectedResource.secretRefs.databaseUrl", "targets": ["railway-secret"], "scopes": ["staging", "prod"], "sensitivity": "secret" },
518
+ { "env": "TREESEED_PLATFORM_RUNNER_TOKEN", "valueFrom": "selectedResource.secretRefs.runnerToken", "targets": ["railway-secret"], "scopes": ["staging", "prod"], "sensitivity": "secret" },
519
+ { "env": "TREESEED_PLATFORM_RUNNER_ID", "valueFrom": "selectedResource.environmentValues.runnerId", "targets": ["railway-var"], "scopes": ["staging", "prod"], "sensitivity": "plain" }
520
+ ]
521
+ }
522
+ ]
523
+ },
197
524
  "relatedBooks": [],
198
525
  "relatedKnowledge": [],
199
526
  "relatedObjectives": []