@treeseed/sdk 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +46 -0
  3. package/dist/operations/providers/default.js +1 -1
  4. package/dist/operations/services/config-runtime.d.ts +49 -42
  5. package/dist/operations/services/config-runtime.js +465 -142
  6. package/dist/operations/services/deploy.d.ts +298 -0
  7. package/dist/operations/services/deploy.js +381 -137
  8. package/dist/operations/services/git-workflow.d.ts +9 -0
  9. package/dist/operations/services/git-workflow.js +32 -0
  10. package/dist/operations/services/github-api.d.ts +115 -0
  11. package/dist/operations/services/github-api.js +455 -0
  12. package/dist/operations/services/github-automation.d.ts +19 -33
  13. package/dist/operations/services/github-automation.js +44 -131
  14. package/dist/operations/services/key-agent.d.ts +20 -1
  15. package/dist/operations/services/key-agent.js +267 -102
  16. package/dist/operations/services/knowledge-coop-launch.d.ts +2 -3
  17. package/dist/operations/services/knowledge-coop-launch.js +26 -12
  18. package/dist/operations/services/project-platform.d.ts +157 -150
  19. package/dist/operations/services/project-platform.js +129 -26
  20. package/dist/operations/services/railway-api.d.ts +244 -0
  21. package/dist/operations/services/railway-api.js +882 -0
  22. package/dist/operations/services/railway-deploy.d.ts +171 -27
  23. package/dist/operations/services/railway-deploy.js +672 -172
  24. package/dist/operations/services/runtime-tools.d.ts +18 -0
  25. package/dist/operations/services/runtime-tools.js +19 -6
  26. package/dist/operations/services/workspace-preflight.js +2 -2
  27. package/dist/platform/contracts.d.ts +7 -0
  28. package/dist/platform/deploy-config.js +23 -0
  29. package/dist/platform/deploy-runtime.d.ts +1 -0
  30. package/dist/platform/deploy-runtime.js +7 -9
  31. package/dist/platform/env.yaml +10 -9
  32. package/dist/platform/environment.js +4 -0
  33. package/dist/platform/plugin.d.ts +6 -0
  34. package/dist/platform/plugins/constants.d.ts +1 -0
  35. package/dist/platform/plugins/constants.js +1 -0
  36. package/dist/platform/plugins/runtime.d.ts +4 -0
  37. package/dist/platform/plugins/runtime.js +8 -1
  38. package/dist/platform/published-content.js +27 -4
  39. package/dist/platform/tenant/runtime-config.js +33 -24
  40. package/dist/plugin-default.d.ts +1 -0
  41. package/dist/plugin-default.js +1 -0
  42. package/dist/reconcile/builtin-adapters.d.ts +3 -0
  43. package/dist/reconcile/builtin-adapters.js +2093 -0
  44. package/dist/reconcile/contracts.d.ts +155 -0
  45. package/dist/reconcile/contracts.js +0 -0
  46. package/dist/reconcile/desired-state.d.ts +179 -0
  47. package/dist/reconcile/desired-state.js +319 -0
  48. package/dist/reconcile/engine.d.ts +405 -0
  49. package/dist/reconcile/engine.js +356 -0
  50. package/dist/reconcile/errors.d.ts +5 -0
  51. package/dist/reconcile/errors.js +13 -0
  52. package/dist/reconcile/index.d.ts +7 -0
  53. package/dist/reconcile/index.js +7 -0
  54. package/dist/reconcile/registry.d.ts +7 -0
  55. package/dist/reconcile/registry.js +64 -0
  56. package/dist/reconcile/state.d.ts +7 -0
  57. package/dist/reconcile/state.js +303 -0
  58. package/dist/reconcile/units.d.ts +6 -0
  59. package/dist/reconcile/units.js +68 -0
  60. package/dist/scripts/config-treeseed.js +27 -19
  61. package/dist/scripts/tenant-deploy.js +35 -14
  62. package/dist/workflow/operations.js +127 -22
  63. package/dist/workflow-support.d.ts +3 -1
  64. package/dist/workflow-support.js +50 -0
  65. package/dist/workflow.d.ts +2 -0
  66. package/package.json +7 -1
@@ -0,0 +1,356 @@
1
+ import { createTreeseedReconcileRegistry } from "./registry.js";
2
+ import { deriveTreeseedDesiredUnits } from "./desired-state.js";
3
+ import { ensureTreeseedPersistedUnitState, desiredUnitSpecHash, loadTreeseedReconcileState, updateTreeseedPersistedUnitState, writeTreeseedReconcileState } from "./state.js";
4
+ import { reverseTopologicallySortedUnits, topologicallySortDesiredUnits } from "./units.js";
5
+ function nowIso() {
6
+ return (/* @__PURE__ */ new Date()).toISOString();
7
+ }
8
+ function toErrorMessage(error) {
9
+ return error instanceof Error ? error.message : String(error);
10
+ }
11
+ function wrapAdapterFailure(stage, provider, unitType, unitId, error) {
12
+ const message = `Treeseed reconcile adapter failed during ${stage} for ${provider}:${unitType} (${unitId}): ${toErrorMessage(error)}`;
13
+ const wrapped = new Error(message);
14
+ if (error instanceof Error && "stack" in error && typeof error.stack === "string") {
15
+ wrapped.stack = error.stack;
16
+ }
17
+ throw wrapped;
18
+ }
19
+ function formatVerificationFailure(verification) {
20
+ const details = [
21
+ verification.missing.length > 0 ? `missing: ${verification.missing.join("; ")}` : null,
22
+ verification.drifted.length > 0 ? `drifted: ${verification.drifted.join("; ")}` : null,
23
+ verification.warnings.length > 0 ? `warnings: ${verification.warnings.join("; ")}` : null
24
+ ].filter(Boolean);
25
+ return details.length > 0 ? `Verification failed (${details.join(" | ")})` : "Verification failed.";
26
+ }
27
+ function createRunContext(tenantRoot, target, launchEnv, write) {
28
+ const { deployConfig } = deriveTreeseedDesiredUnits({ tenantRoot, target });
29
+ return {
30
+ tenantRoot,
31
+ target,
32
+ deployConfig,
33
+ launchEnv,
34
+ write,
35
+ session: /* @__PURE__ */ new Map()
36
+ };
37
+ }
38
+ function persistResult(reconcileState, previous, result) {
39
+ updateTreeseedPersistedUnitState(reconcileState, {
40
+ ...previous,
41
+ desiredSpecHash: desiredUnitSpecHash(result.unit),
42
+ lastObservedAt: nowIso(),
43
+ lastReconciledAt: nowIso(),
44
+ lastVerifiedAt: result.verification?.verified ? nowIso() : previous.lastVerifiedAt,
45
+ lastStatus: result.verification?.verified ? "ready" : result.observed.status,
46
+ lastObservedState: result.observed.live,
47
+ lastReconciledState: result.state,
48
+ lastDiff: result.diff,
49
+ lastVerification: result.verification,
50
+ lastAction: result.action,
51
+ resourceLocators: result.resourceLocators,
52
+ warnings: [...result.warnings, ...result.verification?.warnings ?? []],
53
+ error: result.verification?.verified === false ? formatVerificationFailure(result.verification) : null
54
+ });
55
+ }
56
+ async function verifyPlanUnits({
57
+ plans,
58
+ registry,
59
+ context,
60
+ state,
61
+ write
62
+ }) {
63
+ const verificationMap = /* @__PURE__ */ new Map();
64
+ context.session.set("treeseed:verification-results", verificationMap);
65
+ const verificationResults = /* @__PURE__ */ new Map();
66
+ for (const plan of plans) {
67
+ write?.(`Verifying ${plan.unit.provider}:${plan.unit.unitType}...`);
68
+ const adapter = registry.get(plan.unit.unitType, plan.unit.provider);
69
+ const persisted = ensureTreeseedPersistedUnitState(state, plan.unit);
70
+ const postconditions = await Promise.resolve(adapter.requiredPostconditions?.({
71
+ context,
72
+ unit: plan.unit,
73
+ persistedState: persisted
74
+ }) ?? []);
75
+ let verification;
76
+ try {
77
+ verification = await Promise.resolve(adapter.verify({
78
+ context,
79
+ unit: plan.unit,
80
+ persistedState: persisted,
81
+ observed: plan.observed,
82
+ diff: plan.diff,
83
+ result: null,
84
+ postconditions
85
+ }));
86
+ } catch (error) {
87
+ wrapAdapterFailure("verify", plan.unit.provider, plan.unit.unitType, plan.unit.unitId, error);
88
+ }
89
+ verificationMap.set(plan.unit.unitId, verification);
90
+ verificationResults.set(plan.unit.unitId, {
91
+ postconditions,
92
+ verification
93
+ });
94
+ }
95
+ return verificationResults;
96
+ }
97
+ async function observeTreeseedUnits({
98
+ tenantRoot,
99
+ target,
100
+ env = process.env,
101
+ write
102
+ }) {
103
+ const { units, deployConfig } = deriveTreeseedDesiredUnits({ tenantRoot, target });
104
+ const registry = createTreeseedReconcileRegistry(deployConfig);
105
+ const reconcileState = loadTreeseedReconcileState(tenantRoot, target);
106
+ const context = createRunContext(tenantRoot, target, env, write);
107
+ const observations = /* @__PURE__ */ new Map();
108
+ for (const unit of topologicallySortDesiredUnits(units)) {
109
+ write?.(`Observing ${unit.provider}:${unit.unitType}...`);
110
+ const adapter = registry.get(unit.unitType, unit.provider);
111
+ const persisted = ensureTreeseedPersistedUnitState(reconcileState, unit);
112
+ let observed;
113
+ try {
114
+ observed = await Promise.resolve(adapter.observe({
115
+ context,
116
+ unit,
117
+ persistedState: persisted
118
+ }));
119
+ } catch (error) {
120
+ wrapAdapterFailure("observe", unit.provider, unit.unitType, unit.unitId, error);
121
+ }
122
+ observations.set(unit.unitId, observed);
123
+ }
124
+ return {
125
+ units,
126
+ observations,
127
+ state: reconcileState,
128
+ deployConfig
129
+ };
130
+ }
131
+ async function planTreeseedReconciliation({
132
+ tenantRoot,
133
+ target,
134
+ env = process.env,
135
+ write
136
+ }) {
137
+ const observed = await observeTreeseedUnits({ tenantRoot, target, env, write });
138
+ const registry = createTreeseedReconcileRegistry(observed.deployConfig);
139
+ const context = createRunContext(tenantRoot, target, env, write);
140
+ const plans = [];
141
+ for (const unit of topologicallySortDesiredUnits(observed.units)) {
142
+ const adapter = registry.get(unit.unitType, unit.provider);
143
+ const persisted = ensureTreeseedPersistedUnitState(observed.state, unit);
144
+ const observation = observed.observations.get(unit.unitId);
145
+ let diff;
146
+ try {
147
+ diff = await Promise.resolve(adapter.plan({
148
+ context,
149
+ unit,
150
+ persistedState: persisted,
151
+ observed: observation
152
+ }));
153
+ } catch (error) {
154
+ wrapAdapterFailure("plan", unit.provider, unit.unitType, unit.unitId, error);
155
+ }
156
+ plans.push({
157
+ unit,
158
+ observed: observation,
159
+ diff,
160
+ persisted
161
+ });
162
+ }
163
+ return {
164
+ ...observed,
165
+ plans
166
+ };
167
+ }
168
+ async function reconcileTreeseedTarget({
169
+ tenantRoot,
170
+ target,
171
+ env = process.env,
172
+ write
173
+ }) {
174
+ const planned = await planTreeseedReconciliation({ tenantRoot, target, env, write });
175
+ const registry = createTreeseedReconcileRegistry(planned.deployConfig);
176
+ const context = createRunContext(tenantRoot, target, env, write);
177
+ const results = [];
178
+ const verificationMap = /* @__PURE__ */ new Map();
179
+ context.session.set("treeseed:verification-results", verificationMap);
180
+ for (const plan of planned.plans) {
181
+ write?.(`Reconciling ${plan.unit.provider}:${plan.unit.unitType}...`);
182
+ const adapter = registry.get(plan.unit.unitType, plan.unit.provider);
183
+ const persisted = ensureTreeseedPersistedUnitState(planned.state, plan.unit);
184
+ try {
185
+ await Promise.resolve(adapter.validate?.({
186
+ context,
187
+ unit: plan.unit,
188
+ persistedState: persisted
189
+ }));
190
+ } catch (error) {
191
+ wrapAdapterFailure("validate", plan.unit.provider, plan.unit.unitType, plan.unit.unitId, error);
192
+ }
193
+ let result;
194
+ try {
195
+ result = await Promise.resolve(adapter.reconcile({
196
+ context,
197
+ unit: plan.unit,
198
+ persistedState: persisted,
199
+ observed: plan.observed,
200
+ diff: plan.diff
201
+ }));
202
+ } catch (error) {
203
+ wrapAdapterFailure("reconcile", plan.unit.provider, plan.unit.unitType, plan.unit.unitId, error);
204
+ }
205
+ write?.(`Verifying ${plan.unit.provider}:${plan.unit.unitType}...`);
206
+ const postconditions = await Promise.resolve(adapter.requiredPostconditions?.({
207
+ context,
208
+ unit: plan.unit,
209
+ persistedState: persisted
210
+ }) ?? []);
211
+ let verification;
212
+ try {
213
+ verification = await Promise.resolve(adapter.verify({
214
+ context,
215
+ unit: plan.unit,
216
+ persistedState: persisted,
217
+ observed: result.observed,
218
+ diff: plan.diff,
219
+ result,
220
+ postconditions
221
+ }));
222
+ } catch (error) {
223
+ wrapAdapterFailure("verify", plan.unit.provider, plan.unit.unitType, plan.unit.unitId, error);
224
+ }
225
+ const verifiedResult = {
226
+ ...result,
227
+ verification,
228
+ warnings: [
229
+ ...result.warnings,
230
+ ...verification.warnings ?? []
231
+ ]
232
+ };
233
+ verificationMap.set(plan.unit.unitId, verification);
234
+ if (!verification.verified) {
235
+ persistResult(planned.state, persisted, verifiedResult);
236
+ writeTreeseedReconcileState(tenantRoot, planned.state);
237
+ throw new Error(`Treeseed reconcile verification failed for ${plan.unit.provider}:${plan.unit.unitType} (${plan.unit.unitId}): ${formatVerificationFailure(verification)}`);
238
+ }
239
+ persistResult(planned.state, persisted, verifiedResult);
240
+ results.push(verifiedResult);
241
+ }
242
+ writeTreeseedReconcileState(tenantRoot, planned.state);
243
+ return {
244
+ target,
245
+ units: planned.units,
246
+ plans: planned.plans,
247
+ results,
248
+ state: planned.state
249
+ };
250
+ }
251
+ async function destroyTreeseedTargetUnits({
252
+ tenantRoot,
253
+ target,
254
+ env = process.env,
255
+ write
256
+ }) {
257
+ const { units, deployConfig } = deriveTreeseedDesiredUnits({ tenantRoot, target });
258
+ const registry = createTreeseedReconcileRegistry(deployConfig);
259
+ const reconcileState = loadTreeseedReconcileState(tenantRoot, target);
260
+ const context = createRunContext(tenantRoot, target, env, write);
261
+ const results = [];
262
+ for (const unit of reverseTopologicallySortedUnits(units)) {
263
+ const adapter = registry.get(unit.unitType, unit.provider);
264
+ if (!adapter.destroy) {
265
+ continue;
266
+ }
267
+ write?.(`Destroying ${unit.provider}:${unit.unitType}...`);
268
+ const persisted = ensureTreeseedPersistedUnitState(reconcileState, unit);
269
+ let observed;
270
+ try {
271
+ observed = await Promise.resolve(adapter.observe({
272
+ context,
273
+ unit,
274
+ persistedState: persisted
275
+ }));
276
+ } catch (error) {
277
+ wrapAdapterFailure("observe", unit.provider, unit.unitType, unit.unitId, error);
278
+ }
279
+ let result;
280
+ try {
281
+ result = await Promise.resolve(adapter.destroy({
282
+ context,
283
+ unit,
284
+ persistedState: persisted,
285
+ observed
286
+ }));
287
+ } catch (error) {
288
+ wrapAdapterFailure("destroy", unit.provider, unit.unitType, unit.unitId, error);
289
+ }
290
+ results.push(result);
291
+ }
292
+ return { target, results };
293
+ }
294
+ async function collectTreeseedReconcileStatus({
295
+ tenantRoot,
296
+ target,
297
+ env = process.env
298
+ }) {
299
+ const observed = await observeTreeseedUnits({ tenantRoot, target, env });
300
+ const registry = createTreeseedReconcileRegistry(observed.deployConfig);
301
+ const context = createRunContext(tenantRoot, target, env);
302
+ const plans = await Promise.all(observed.units.map(async (unit) => {
303
+ const adapter = registry.get(unit.unitType, unit.provider);
304
+ const persisted = ensureTreeseedPersistedUnitState(observed.state, unit);
305
+ const observation = observed.observations.get(unit.unitId);
306
+ const diff = await Promise.resolve(adapter.plan({
307
+ context,
308
+ unit,
309
+ persistedState: persisted,
310
+ observed: observation
311
+ }));
312
+ return {
313
+ unit,
314
+ observed: observation,
315
+ diff,
316
+ persisted
317
+ };
318
+ }));
319
+ const verificationResults = await verifyPlanUnits({
320
+ plans,
321
+ registry,
322
+ context,
323
+ state: observed.state
324
+ });
325
+ const units = observed.units.map((unit) => {
326
+ const observation = observed.observations.get(unit.unitId);
327
+ const verification = verificationResults.get(unit.unitId)?.verification ?? null;
328
+ return {
329
+ unitId: unit.unitId,
330
+ unitType: unit.unitType,
331
+ provider: unit.provider,
332
+ status: observation.status,
333
+ exists: observation.exists,
334
+ locators: observation.locators,
335
+ warnings: [...observation.warnings, ...verification?.warnings ?? []],
336
+ verification
337
+ };
338
+ });
339
+ const ready = units.every((unit) => unit.verification?.verified === true);
340
+ const blockers = units.filter((unit) => unit.verification?.verified !== true).map((unit) => `Reconcile unit ${unit.provider}:${unit.unitType} is unverified: ${formatVerificationFailure(unit.verification)}.`);
341
+ const warnings = units.flatMap((unit) => unit.warnings);
342
+ return {
343
+ target,
344
+ ready,
345
+ blockers,
346
+ warnings,
347
+ units
348
+ };
349
+ }
350
+ export {
351
+ collectTreeseedReconcileStatus,
352
+ destroyTreeseedTargetUnits,
353
+ observeTreeseedUnits,
354
+ planTreeseedReconciliation,
355
+ reconcileTreeseedTarget
356
+ };
@@ -0,0 +1,5 @@
1
+ export declare class TreeseedReconcileError extends Error {
2
+ code: string;
3
+ unitId: string | null;
4
+ constructor(code: string, message: string, unitId?: string | null);
5
+ }
@@ -0,0 +1,13 @@
1
+ class TreeseedReconcileError extends Error {
2
+ code;
3
+ unitId;
4
+ constructor(code, message, unitId = null) {
5
+ super(message);
6
+ this.name = "TreeseedReconcileError";
7
+ this.code = code;
8
+ this.unitId = unitId;
9
+ }
10
+ }
11
+ export {
12
+ TreeseedReconcileError
13
+ };
@@ -0,0 +1,7 @@
1
+ export * from './contracts.ts';
2
+ export * from './desired-state.ts';
3
+ export * from './engine.ts';
4
+ export * from './errors.ts';
5
+ export * from './registry.ts';
6
+ export * from './state.ts';
7
+ export * from './units.ts';
@@ -0,0 +1,7 @@
1
+ export * from "./contracts.js";
2
+ export * from "./desired-state.js";
3
+ export * from "./engine.js";
4
+ export * from "./errors.js";
5
+ export * from "./registry.js";
6
+ export * from "./state.js";
7
+ export * from "./units.js";
@@ -0,0 +1,7 @@
1
+ import type { TreeseedDeployConfig } from '../platform/contracts.ts';
2
+ import type { TreeseedReconcileAdapter, TreeseedReconcileProviderId, TreeseedReconcileUnitType } from './contracts.ts';
3
+ export type TreeseedReconcileRegistry = {
4
+ adapters: TreeseedReconcileAdapter[];
5
+ get(unitType: TreeseedReconcileUnitType, providerId: TreeseedReconcileProviderId): TreeseedReconcileAdapter;
6
+ };
7
+ export declare function createTreeseedReconcileRegistry(config: TreeseedDeployConfig): TreeseedReconcileRegistry;
@@ -0,0 +1,64 @@
1
+ import { loadTreeseedPlugins } from "../platform/plugins/runtime.js";
2
+ import { createCloudflareReconcileAdapters, createRailwayReconcileAdapters } from "./builtin-adapters.js";
3
+ function normalizeAdapterContribution(contribution, context) {
4
+ if (typeof contribution === "function") {
5
+ return contribution(context);
6
+ }
7
+ return contribution;
8
+ }
9
+ function loadPluginReconcileAdapters(config) {
10
+ const tenantRoot = config.__tenantRoot ?? process.cwd();
11
+ const plugins = loadTreeseedPlugins(config);
12
+ const adapters = [];
13
+ for (const entry of plugins) {
14
+ const plugin = entry.plugin;
15
+ const contributions = plugin.reconcileAdapters;
16
+ if (!contributions || typeof contributions !== "object") {
17
+ continue;
18
+ }
19
+ for (const contribution of Object.values(contributions)) {
20
+ const resolved = normalizeAdapterContribution(contribution, {
21
+ projectRoot: tenantRoot,
22
+ deployConfig: config,
23
+ tenantConfig: void 0,
24
+ pluginConfig: entry.config ?? {}
25
+ });
26
+ if (!resolved) {
27
+ continue;
28
+ }
29
+ adapters.push(resolved);
30
+ }
31
+ }
32
+ return adapters;
33
+ }
34
+ function createTreeseedReconcileRegistry(config) {
35
+ const adapters = [
36
+ ...createCloudflareReconcileAdapters(),
37
+ ...createRailwayReconcileAdapters(),
38
+ ...loadPluginReconcileAdapters(config)
39
+ ];
40
+ const seenBindings = /* @__PURE__ */ new Map();
41
+ for (const adapter of adapters) {
42
+ for (const unitType of adapter.unitTypes) {
43
+ const bindingKey = `${adapter.providerId}:${unitType}`;
44
+ const existing = seenBindings.get(bindingKey);
45
+ if (existing) {
46
+ throw new Error(`Duplicate Treeseed reconcile adapter binding for ${bindingKey} (${existing}, ${adapter.providerId}).`);
47
+ }
48
+ seenBindings.set(bindingKey, adapter.providerId);
49
+ }
50
+ }
51
+ return {
52
+ adapters,
53
+ get(unitType, providerId) {
54
+ const adapter = adapters.find((candidate) => candidate.supports(unitType, providerId));
55
+ if (!adapter) {
56
+ throw new Error(`Treeseed reconcile adapter missing for ${providerId}:${unitType}.`);
57
+ }
58
+ return adapter;
59
+ }
60
+ };
61
+ }
62
+ export {
63
+ createTreeseedReconcileRegistry
64
+ };
@@ -0,0 +1,7 @@
1
+ import type { TreeseedDesiredUnit, TreeseedReconcileStateRecord, TreeseedReconcileTarget, TreeseedUnitPersistedState } from './contracts.ts';
2
+ export declare function migrateLegacyDeployStateUnits(legacyState: Record<string, any>, target: TreeseedReconcileTarget): Record<string, TreeseedUnitPersistedState>;
3
+ export declare function loadTreeseedReconcileState(tenantRoot: string, target: TreeseedReconcileTarget): TreeseedReconcileStateRecord;
4
+ export declare function writeTreeseedReconcileState(tenantRoot: string, reconcileState: TreeseedReconcileStateRecord): void;
5
+ export declare function ensureTreeseedPersistedUnitState(reconcileState: TreeseedReconcileStateRecord, unit: TreeseedDesiredUnit): TreeseedUnitPersistedState;
6
+ export declare function updateTreeseedPersistedUnitState(reconcileState: TreeseedReconcileStateRecord, state: TreeseedUnitPersistedState): void;
7
+ export declare function desiredUnitSpecHash(unit: TreeseedDesiredUnit): string;