@treeseed/sdk 0.13.0-rc.70 → 0.13.0-rc.72
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.
- package/dist/.treeseed-build-complete.json +1 -1
- package/dist/content/validation/content-model-schemas.d.ts +1 -1
- package/dist/deployment/hosted-topology.d.ts +826 -0
- package/dist/deployment/hosted-topology.js +246 -0
- package/dist/deployment/index.d.ts +1 -0
- package/dist/deployment/index.js +1 -0
- package/dist/operator-contracts/canonical-command-tree.js +10 -0
- package/dist/operator-contracts/catalog/communication-operations.d.ts +1 -1
- package/dist/operator-contracts/catalog/hosted-topology-operations.d.ts +168 -0
- package/dist/operator-contracts/catalog/hosted-topology-operations.js +77 -0
- package/dist/operator-contracts/communication/contracts.d.ts +2 -2
- package/dist/operator-contracts/control-plane-operations.d.ts +171 -1
- package/dist/operator-contracts/control-plane-operations.js +3 -4
- package/dist/secrets-capability/service-provider-contracts.js +10 -9
- package/package.json +1 -1
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { deploymentDigest } from "./canonical.js";
|
|
3
|
+
const identifier = z.string().regex(/^[a-z][a-z0-9.-]{1,63}$/u);
|
|
4
|
+
const digest = z.string().regex(/^sha256:[a-f0-9]{64}$/u);
|
|
5
|
+
const gitCommit = z.string().regex(/^[a-f0-9]{40}$/u);
|
|
6
|
+
const timestamp = z.string().datetime();
|
|
7
|
+
const hostedProviderSchema = z.enum(["cloudflare", "railway"]);
|
|
8
|
+
const hostedResourceKindSchema = z.enum([
|
|
9
|
+
"admin-application",
|
|
10
|
+
"dns-record",
|
|
11
|
+
"tls-policy",
|
|
12
|
+
"api-proxy",
|
|
13
|
+
"control-plane-api",
|
|
14
|
+
"postgresql",
|
|
15
|
+
"operations-runner",
|
|
16
|
+
"treedx-service"
|
|
17
|
+
]);
|
|
18
|
+
const parameterSchema = z.union([
|
|
19
|
+
z.object({ input: identifier }).strict(),
|
|
20
|
+
z.object({ artifact: identifier }).strict(),
|
|
21
|
+
z.object({ resourceOutput: z.object({ resourceId: identifier, output: identifier }).strict() }).strict(),
|
|
22
|
+
z.object({ literal: z.union([z.string().max(4096), z.number().finite(), z.boolean()]) }).strict()
|
|
23
|
+
]);
|
|
24
|
+
const resourceProviderKinds = {
|
|
25
|
+
cloudflare: /* @__PURE__ */ new Set(["admin-application", "dns-record", "tls-policy", "api-proxy"]),
|
|
26
|
+
railway: /* @__PURE__ */ new Set(["control-plane-api", "postgresql", "operations-runner", "treedx-service"])
|
|
27
|
+
};
|
|
28
|
+
const sensitiveKey = /(?:credential|password|private.?key|registration.?code|secret|token)/iu;
|
|
29
|
+
const personalPath = /(?:^|[\s'"`:=])(?:\/home\/[^/\s]+|\/Users\/[^/\s]+|[A-Za-z]:\\Users\\[^\\\s]+)/u;
|
|
30
|
+
const hostedTopologyDeclarationSchema = z.object({
|
|
31
|
+
schemaVersion: z.literal("treeseed.hosted-topology/v1"),
|
|
32
|
+
id: identifier,
|
|
33
|
+
environment: z.enum(["staging", "production"]),
|
|
34
|
+
mutation: z.literal("approval-required"),
|
|
35
|
+
platform: z.object({ repository: z.literal("treeseed-ai/platform"), commit: gitCommit }).strict(),
|
|
36
|
+
providerConnections: z.record(hostedProviderSchema, z.object({ connectionRef: identifier }).strict()),
|
|
37
|
+
artifacts: z.record(identifier, z.object({ digest, source: z.string().url() }).strict()),
|
|
38
|
+
resources: z.array(z.object({
|
|
39
|
+
id: identifier,
|
|
40
|
+
provider: hostedProviderSchema,
|
|
41
|
+
kind: hostedResourceKindSchema,
|
|
42
|
+
dependsOn: z.array(identifier).default([]),
|
|
43
|
+
parameters: z.record(identifier, parameterSchema).default({}),
|
|
44
|
+
adoption: z.object({ mode: z.literal("adopt-or-create"), externalIdInput: identifier.optional(), replacement: z.literal("forbidden") }).strict()
|
|
45
|
+
}).strict()).min(1)
|
|
46
|
+
}).strict().superRefine((declaration, context) => {
|
|
47
|
+
const ids = declaration.resources.map(({ id }) => id);
|
|
48
|
+
if (new Set(ids).size !== ids.length) context.addIssue({ code: z.ZodIssueCode.custom, path: ["resources"], message: "Hosted topology resource identities must be unique." });
|
|
49
|
+
for (const [index, resource] of declaration.resources.entries()) {
|
|
50
|
+
if (!resourceProviderKinds[resource.provider].has(resource.kind)) context.addIssue({ code: z.ZodIssueCode.custom, path: ["resources", index, "kind"], message: `${resource.kind} is not owned by ${resource.provider}.` });
|
|
51
|
+
for (const dependency of resource.dependsOn) if (!ids.includes(dependency)) context.addIssue({ code: z.ZodIssueCode.custom, path: ["resources", index, "dependsOn"], message: `Unknown hosted resource dependency ${dependency}.` });
|
|
52
|
+
for (const key of Object.keys(resource.parameters)) if (sensitiveKey.test(key)) context.addIssue({ code: z.ZodIssueCode.custom, path: ["resources", index, "parameters", key], message: "Hosted topology parameters cannot carry credential-like values." });
|
|
53
|
+
for (const [key, parameter] of Object.entries(resource.parameters)) if ("literal" in parameter && typeof parameter.literal === "string" && personalPath.test(parameter.literal)) context.addIssue({ code: z.ZodIssueCode.custom, path: ["resources", index, "parameters", key], message: "Hosted topology parameters cannot contain personal filesystem paths." });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const hostedResourceObservationSchema = z.object({
|
|
57
|
+
resourceId: identifier,
|
|
58
|
+
provider: hostedProviderSchema,
|
|
59
|
+
kind: hostedResourceKindSchema,
|
|
60
|
+
providerResourceId: z.string().min(1).max(512).nullable(),
|
|
61
|
+
state: z.enum(["missing", "healthy", "degraded"]),
|
|
62
|
+
managedBy: z.enum(["treeseed", "external"]).nullable(),
|
|
63
|
+
observedDigest: digest.nullable(),
|
|
64
|
+
observedAt: timestamp
|
|
65
|
+
}).strict();
|
|
66
|
+
const hostedTopologyPlanSchema = z.object({
|
|
67
|
+
schemaVersion: z.literal("treeseed.hosted-topology-plan/v1"),
|
|
68
|
+
planId: z.string().regex(/^topology-plan-[a-f0-9]{16}$/u),
|
|
69
|
+
planDigest: digest,
|
|
70
|
+
declarationDigest: digest,
|
|
71
|
+
topologyId: identifier,
|
|
72
|
+
environment: z.enum(["staging", "production"]),
|
|
73
|
+
platformCommit: gitCommit,
|
|
74
|
+
actions: z.array(z.object({
|
|
75
|
+
resourceId: identifier,
|
|
76
|
+
provider: hostedProviderSchema,
|
|
77
|
+
kind: hostedResourceKindSchema,
|
|
78
|
+
action: z.enum(["create", "adopt", "update", "noop"]),
|
|
79
|
+
desiredDigest: digest,
|
|
80
|
+
previousDigest: digest.nullable(),
|
|
81
|
+
providerResourceId: z.string().min(1).max(512).nullable()
|
|
82
|
+
}).strict()),
|
|
83
|
+
blockers: z.array(z.object({ code: z.enum(["connection-unavailable", "dependency-cycle", "observation-unhealthy", "adoption-drift"]), resourceId: identifier.optional(), message: z.string().min(1) }).strict()),
|
|
84
|
+
approvalRequired: z.boolean(),
|
|
85
|
+
executable: z.literal(false)
|
|
86
|
+
}).strict();
|
|
87
|
+
const hostedTopologyApprovalSchema = z.object({
|
|
88
|
+
schemaVersion: z.literal("treeseed.hosted-topology-approval/v1"),
|
|
89
|
+
planDigest: digest,
|
|
90
|
+
environment: z.enum(["staging", "production"]),
|
|
91
|
+
decision: z.literal("approved"),
|
|
92
|
+
approvedBy: z.string().min(1).max(256),
|
|
93
|
+
approvedAt: timestamp
|
|
94
|
+
}).strict();
|
|
95
|
+
const authorizedHostedTopologyPlanSchema = hostedTopologyPlanSchema.extend({
|
|
96
|
+
executable: z.literal(true),
|
|
97
|
+
approval: hostedTopologyApprovalSchema.nullable()
|
|
98
|
+
}).omit({ approvalRequired: true });
|
|
99
|
+
const hostedTopologyReceiptSchema = z.object({
|
|
100
|
+
schemaVersion: z.literal("treeseed.hosted-topology-receipt/v1"),
|
|
101
|
+
receiptId: z.string().regex(/^topology-receipt-[a-f0-9]{16}$/u),
|
|
102
|
+
planDigest: digest,
|
|
103
|
+
declarationDigest: digest,
|
|
104
|
+
topologyId: identifier,
|
|
105
|
+
environment: z.enum(["staging", "production"]),
|
|
106
|
+
platformCommit: gitCommit,
|
|
107
|
+
resources: z.array(hostedResourceObservationSchema),
|
|
108
|
+
previousResources: z.array(hostedResourceObservationSchema),
|
|
109
|
+
state: z.literal("known-good"),
|
|
110
|
+
completedAt: timestamp
|
|
111
|
+
}).strict();
|
|
112
|
+
const hostedTopologyRollbackSchema = z.object({
|
|
113
|
+
schemaVersion: z.literal("treeseed.hosted-topology-rollback/v1"),
|
|
114
|
+
rollbackId: z.string().regex(/^topology-rollback-[a-f0-9]{16}$/u),
|
|
115
|
+
sourceReceiptId: z.string().regex(/^topology-receipt-[a-f0-9]{16}$/u),
|
|
116
|
+
environment: z.enum(["staging", "production"]),
|
|
117
|
+
operations: z.array(z.object({
|
|
118
|
+
resourceId: identifier,
|
|
119
|
+
action: z.enum(["restore", "delete-created", "noop"]),
|
|
120
|
+
providerResourceId: z.string().min(1).max(512),
|
|
121
|
+
targetDigest: digest.nullable()
|
|
122
|
+
}).strict()),
|
|
123
|
+
rollbackDigest: digest
|
|
124
|
+
}).strict();
|
|
125
|
+
const hostedTopologyRollbackApprovalSchema = z.object({
|
|
126
|
+
schemaVersion: z.literal("treeseed.hosted-topology-rollback-approval/v1"),
|
|
127
|
+
rollbackDigest: digest,
|
|
128
|
+
environment: z.enum(["staging", "production"]),
|
|
129
|
+
decision: z.literal("approved"),
|
|
130
|
+
approvedBy: z.string().min(1).max(256),
|
|
131
|
+
approvedAt: timestamp
|
|
132
|
+
}).strict();
|
|
133
|
+
function observationMap(items, declaration) {
|
|
134
|
+
const resources = new Map(declaration.resources.map((resource) => [resource.id, resource]));
|
|
135
|
+
const observations = /* @__PURE__ */ new Map();
|
|
136
|
+
for (const input of items) {
|
|
137
|
+
const item = hostedResourceObservationSchema.parse(input), resource = resources.get(item.resourceId);
|
|
138
|
+
if (observations.has(item.resourceId)) throw new Error(`Duplicate hosted resource observation ${item.resourceId}.`);
|
|
139
|
+
if (!resource) throw new Error(`Unknown hosted resource observation ${item.resourceId}.`);
|
|
140
|
+
if (item.provider !== resource.provider || item.kind !== resource.kind) throw new Error(`Hosted resource observation identity mismatch for ${item.resourceId}.`);
|
|
141
|
+
observations.set(item.resourceId, item);
|
|
142
|
+
}
|
|
143
|
+
return observations;
|
|
144
|
+
}
|
|
145
|
+
function cycleMembers(declaration) {
|
|
146
|
+
const dependencies = new Map(declaration.resources.map(({ id, dependsOn }) => [id, dependsOn]));
|
|
147
|
+
const visiting = /* @__PURE__ */ new Set(), visited = /* @__PURE__ */ new Set(), cycles = /* @__PURE__ */ new Set();
|
|
148
|
+
const visit = (id) => {
|
|
149
|
+
if (visiting.has(id)) {
|
|
150
|
+
cycles.add(id);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (visited.has(id)) return;
|
|
154
|
+
visiting.add(id);
|
|
155
|
+
for (const dependency of dependencies.get(id) ?? []) {
|
|
156
|
+
visit(dependency);
|
|
157
|
+
if (cycles.has(dependency)) cycles.add(id);
|
|
158
|
+
}
|
|
159
|
+
visiting.delete(id);
|
|
160
|
+
visited.add(id);
|
|
161
|
+
};
|
|
162
|
+
for (const id of dependencies.keys()) visit(id);
|
|
163
|
+
return [...cycles].sort();
|
|
164
|
+
}
|
|
165
|
+
function planHostedTopology(input) {
|
|
166
|
+
const declaration = hostedTopologyDeclarationSchema.parse(input.declaration);
|
|
167
|
+
const normalizedDeclaration = {
|
|
168
|
+
...declaration,
|
|
169
|
+
resources: [...declaration.resources].map((resource) => ({ ...resource, dependsOn: [...resource.dependsOn].sort() })).sort((left, right) => left.id.localeCompare(right.id))
|
|
170
|
+
};
|
|
171
|
+
const observations = observationMap(input.observations, normalizedDeclaration);
|
|
172
|
+
const blockers = [];
|
|
173
|
+
for (const [provider, binding] of Object.entries(normalizedDeclaration.providerConnections)) if (!input.availableConnections.includes(binding.connectionRef)) blockers.push({ code: "connection-unavailable", message: `${provider} connection ${binding.connectionRef} is unavailable.` });
|
|
174
|
+
for (const resourceId of cycleMembers(normalizedDeclaration)) blockers.push({ code: "dependency-cycle", resourceId, message: `Hosted resource ${resourceId} participates in a dependency cycle.` });
|
|
175
|
+
const actions = normalizedDeclaration.resources.map((resource) => {
|
|
176
|
+
const desiredDigest = deploymentDigest(resource);
|
|
177
|
+
const observation = observations.get(resource.id);
|
|
178
|
+
let action = "create";
|
|
179
|
+
if (observation?.state === "degraded") blockers.push({ code: "observation-unhealthy", resourceId: resource.id, message: `Hosted resource ${resource.id} is degraded and cannot be reconciled automatically.` });
|
|
180
|
+
if (observation?.state === "healthy") {
|
|
181
|
+
if (observation.observedDigest === desiredDigest) action = observation.managedBy === "external" ? "adopt" : "noop";
|
|
182
|
+
else if (observation.managedBy === "external") blockers.push({ code: "adoption-drift", resourceId: resource.id, message: `External resource ${resource.id} differs from the declaration and cannot be replaced.` });
|
|
183
|
+
else action = "update";
|
|
184
|
+
}
|
|
185
|
+
return { resourceId: resource.id, provider: resource.provider, kind: resource.kind, action, desiredDigest, previousDigest: observation?.observedDigest ?? null, providerResourceId: observation?.providerResourceId ?? null };
|
|
186
|
+
});
|
|
187
|
+
const declarationDigest = deploymentDigest(normalizedDeclaration);
|
|
188
|
+
const core = { declarationDigest, topologyId: declaration.id, environment: declaration.environment, platformCommit: declaration.platform.commit, actions, blockers };
|
|
189
|
+
const planDigest = deploymentDigest(core);
|
|
190
|
+
return hostedTopologyPlanSchema.parse({ schemaVersion: "treeseed.hosted-topology-plan/v1", planId: `topology-plan-${planDigest.slice(7, 23)}`, planDigest, ...core, approvalRequired: actions.some(({ action }) => action !== "noop"), executable: false });
|
|
191
|
+
}
|
|
192
|
+
function authorizeHostedTopologyPlan(planInput, approvalInput) {
|
|
193
|
+
const plan = hostedTopologyPlanSchema.parse(planInput);
|
|
194
|
+
if (plan.blockers.length) throw new Error("Hosted topology plan has unresolved blockers.");
|
|
195
|
+
const approval = approvalInput ? hostedTopologyApprovalSchema.parse(approvalInput) : null;
|
|
196
|
+
if (plan.approvalRequired && !approval) throw new Error("Hosted topology mutation requires environment approval.");
|
|
197
|
+
if (approval && (approval.planDigest !== plan.planDigest || approval.environment !== plan.environment)) throw new Error("Hosted topology approval does not bind the exact plan and environment.");
|
|
198
|
+
const { approvalRequired: _approvalRequired, ...approvedPlan } = plan;
|
|
199
|
+
return authorizedHostedTopologyPlanSchema.parse({ ...approvedPlan, executable: true, approval });
|
|
200
|
+
}
|
|
201
|
+
function verifyHostedTopologyReadback(input) {
|
|
202
|
+
const plan = authorizedHostedTopologyPlanSchema.parse(input.plan);
|
|
203
|
+
const expectedIds = new Set(plan.actions.map(({ resourceId }) => resourceId));
|
|
204
|
+
const resources = input.resources.map((item) => hostedResourceObservationSchema.parse(item)).sort((left, right) => left.resourceId.localeCompare(right.resourceId));
|
|
205
|
+
if (resources.length !== expectedIds.size || new Set(resources.map(({ resourceId }) => resourceId)).size !== resources.length || resources.some(({ resourceId }) => !expectedIds.has(resourceId))) throw new Error("Authoritative read-back must contain each planned hosted resource exactly once.");
|
|
206
|
+
const observed = new Map(resources.map((item) => [item.resourceId, item]));
|
|
207
|
+
for (const action of plan.actions) {
|
|
208
|
+
const item = observed.get(action.resourceId);
|
|
209
|
+
if (!item || item.provider !== action.provider || item.kind !== action.kind || item.state !== "healthy" || !item.providerResourceId || item.observedDigest !== action.desiredDigest) throw new Error(`Authoritative read-back failed for hosted resource ${action.resourceId}.`);
|
|
210
|
+
}
|
|
211
|
+
const completedAt = timestamp.parse(input.completedAt);
|
|
212
|
+
const receiptDigest = deploymentDigest({ planDigest: plan.planDigest, resources, completedAt });
|
|
213
|
+
return hostedTopologyReceiptSchema.parse({ schemaVersion: "treeseed.hosted-topology-receipt/v1", receiptId: `topology-receipt-${receiptDigest.slice(7, 23)}`, planDigest: plan.planDigest, declarationDigest: plan.declarationDigest, topologyId: plan.topologyId, environment: plan.environment, platformCommit: plan.platformCommit, resources, previousResources: input.previousResources.map((item) => hostedResourceObservationSchema.parse(item)).sort((left, right) => left.resourceId.localeCompare(right.resourceId)), state: "known-good", completedAt });
|
|
214
|
+
}
|
|
215
|
+
function planHostedTopologyRollback(receiptInput) {
|
|
216
|
+
const receipt = hostedTopologyReceiptSchema.parse(receiptInput);
|
|
217
|
+
const previous = new Map(receipt.previousResources.map((item) => [item.resourceId, item]));
|
|
218
|
+
const operations = receipt.resources.map((resource) => {
|
|
219
|
+
const prior = previous.get(resource.resourceId);
|
|
220
|
+
return { resourceId: resource.resourceId, action: !prior || prior.state === "missing" ? "delete-created" : prior.observedDigest === resource.observedDigest ? "noop" : "restore", providerResourceId: resource.providerResourceId, targetDigest: prior?.observedDigest ?? null };
|
|
221
|
+
}).sort((left, right) => left.resourceId.localeCompare(right.resourceId));
|
|
222
|
+
const rollbackDigest = deploymentDigest({ sourceReceiptId: receipt.receiptId, operations });
|
|
223
|
+
return hostedTopologyRollbackSchema.parse({ schemaVersion: "treeseed.hosted-topology-rollback/v1", rollbackId: `topology-rollback-${rollbackDigest.slice(7, 23)}`, sourceReceiptId: receipt.receiptId, environment: receipt.environment, operations, rollbackDigest });
|
|
224
|
+
}
|
|
225
|
+
function authorizeHostedTopologyRollback(rollbackInput, approvalInput) {
|
|
226
|
+
const rollback = hostedTopologyRollbackSchema.parse(rollbackInput), approval = hostedTopologyRollbackApprovalSchema.parse(approvalInput);
|
|
227
|
+
if (approval.rollbackDigest !== rollback.rollbackDigest || approval.environment !== rollback.environment) throw new Error("Hosted topology rollback approval does not bind the exact rollback and environment.");
|
|
228
|
+
return { rollback, approval };
|
|
229
|
+
}
|
|
230
|
+
export {
|
|
231
|
+
authorizeHostedTopologyPlan,
|
|
232
|
+
authorizeHostedTopologyRollback,
|
|
233
|
+
authorizedHostedTopologyPlanSchema,
|
|
234
|
+
hostedProviderSchema,
|
|
235
|
+
hostedResourceKindSchema,
|
|
236
|
+
hostedResourceObservationSchema,
|
|
237
|
+
hostedTopologyApprovalSchema,
|
|
238
|
+
hostedTopologyDeclarationSchema,
|
|
239
|
+
hostedTopologyPlanSchema,
|
|
240
|
+
hostedTopologyReceiptSchema,
|
|
241
|
+
hostedTopologyRollbackApprovalSchema,
|
|
242
|
+
hostedTopologyRollbackSchema,
|
|
243
|
+
planHostedTopology,
|
|
244
|
+
planHostedTopologyRollback,
|
|
245
|
+
verifyHostedTopologyReadback
|
|
246
|
+
};
|
package/dist/deployment/index.js
CHANGED
|
@@ -34,6 +34,10 @@ const operationBindings = {
|
|
|
34
34
|
"platform verify": local("local.platform.verify"),
|
|
35
35
|
"platform workset": local("local.platform.workset"),
|
|
36
36
|
"platform project create": local("local.platform.project.create"),
|
|
37
|
+
"platform topology plan": local("local.platform.topology.plan"),
|
|
38
|
+
"platform topology apply": local("local.platform.topology.apply"),
|
|
39
|
+
"platform topology status": local("local.platform.topology.status"),
|
|
40
|
+
"platform topology rollback": local("local.platform.topology.rollback"),
|
|
37
41
|
"host status": local("local.host.status"),
|
|
38
42
|
"host doctor": local("local.host.doctor"),
|
|
39
43
|
"host plan": local("local.host.plan"),
|
|
@@ -358,6 +362,12 @@ const commandTree = {
|
|
|
358
362
|
{ nodeType: "leaf", segment: "workset", description: "Plan or safely materialize exact primary source checkouts beneath packages/.", kind: "mutation", options: [planOption, { name: "--apply", description: "Apply the frozen workset plan.", type: "boolean" }, { name: "--yes", description: "Confirm the planned checkout mutations.", type: "boolean" }, { name: "--profile", description: "Composable source profile; repeat to union profiles.", type: "string[]" }, { name: "--project", description: "Explicit project slug; repeat to select projects.", type: "string[]" }, { name: "--exclude", description: "Project slug to exclude; repeat as needed.", type: "string[]" }, { name: "--json", description: "Emit the stable command-result envelope.", type: "boolean" }], authorization: { capability: "development.workset", confirmation: "never" }, resultSchemaId: "treeseed.platform-workset-result/v1", execution: local("local.platform.workset") },
|
|
359
363
|
branch("project", [
|
|
360
364
|
{ nodeType: "leaf", segment: "create", description: "Plan or reconcile a project, repository, template, library binding, and live inventory without writing application source into Platform Git.", kind: "mutation", arguments: [{ name: "slug", description: "Portable project slug.", required: true }], options: [planOption, { name: "--apply", description: "Apply the accepted creation plan.", type: "boolean" }, { name: "--yes", description: "Confirm authority-bearing project creation.", type: "boolean" }, { name: "--template", description: "Published template identity.", type: "string", required: true }, { name: "--json", description: "Emit the stable command-result envelope.", type: "boolean" }], authorization: { capability: "projects.create", confirmation: "authority" }, resultSchemaId: "treeseed.platform-project-create-result/v1", execution: local("local.platform.project.create") }
|
|
365
|
+
]),
|
|
366
|
+
branch("topology", [
|
|
367
|
+
{ nodeType: "leaf", segment: "plan", description: "Plan Cloudflare and Railway reconciliation from a portable hosted-topology declaration.", kind: "read", arguments: [{ name: "file", description: "Hosted topology YAML or JSON declaration.", required: true }], resultSchemaId: "treeseed.hosted-topology-plan/v1", execution: local("local.platform.topology.plan") },
|
|
368
|
+
{ nodeType: "leaf", segment: "apply", description: "Apply an exact reviewed hosted-topology plan through the operations runner.", kind: "mutation", arguments: [{ name: "plan", description: "Exact plan JSON file.", required: true }], options: [planOption, { name: "--approval", description: "Exact environment approval JSON file.", type: "string", required: true }, { name: "--yes", description: "Confirm the authority-bearing mutation.", type: "boolean" }], authorization: { capability: "infrastructure.write", confirmation: "authority" }, resultSchemaId: "treeseed.platform-operation/v1", execution: local("local.platform.topology.apply") },
|
|
369
|
+
{ nodeType: "leaf", segment: "status", description: "Read the latest hosted-topology operation and known-good receipt.", kind: "read", resultSchemaId: "treeseed.infrastructure.topology.status.output/v1", execution: local("local.platform.topology.status") },
|
|
370
|
+
{ nodeType: "leaf", segment: "rollback", description: "Restore exact prior hosted-topology state from a known-good receipt.", kind: "mutation", arguments: [{ name: "rollback", description: "Exact rollback JSON file.", required: true }], options: [planOption, { name: "--approval", description: "Exact rollback approval JSON file.", type: "string", required: true }, { name: "--yes", description: "Confirm the destructive rollback.", type: "boolean" }], authorization: { capability: "infrastructure.write", confirmation: "destructive" }, resultSchemaId: "treeseed.platform-operation/v1", execution: local("local.platform.topology.rollback") }
|
|
361
371
|
])
|
|
362
372
|
]),
|
|
363
373
|
branch("dev", [
|
|
@@ -410,8 +410,8 @@ export declare function providerCommunicationLifecycleOperations(): {
|
|
|
410
410
|
assignmentId: string;
|
|
411
411
|
}, {}, {
|
|
412
412
|
providerId: string;
|
|
413
|
-
runnerId: string;
|
|
414
413
|
observedAt: string;
|
|
414
|
+
runnerId: string;
|
|
415
415
|
}, {
|
|
416
416
|
assignmentId: string;
|
|
417
417
|
acknowledgedAt: string;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
2
|
+
readonly plan: import("../control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
3
|
+
teamId: string;
|
|
4
|
+
}, {}, {
|
|
5
|
+
declaration: {
|
|
6
|
+
schemaVersion: "treeseed.hosted-topology/v1";
|
|
7
|
+
resources: {
|
|
8
|
+
id: string;
|
|
9
|
+
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
10
|
+
provider: "cloudflare" | "railway";
|
|
11
|
+
adoption: {
|
|
12
|
+
mode: "adopt-or-create";
|
|
13
|
+
replacement: "forbidden";
|
|
14
|
+
externalIdInput?: string | undefined;
|
|
15
|
+
};
|
|
16
|
+
dependsOn?: string[] | undefined;
|
|
17
|
+
parameters?: Record<string, {
|
|
18
|
+
input: string;
|
|
19
|
+
} | {
|
|
20
|
+
artifact: string;
|
|
21
|
+
} | {
|
|
22
|
+
resourceOutput: {
|
|
23
|
+
resourceId: string;
|
|
24
|
+
output: string;
|
|
25
|
+
};
|
|
26
|
+
} | {
|
|
27
|
+
literal: string | number | boolean;
|
|
28
|
+
}> | undefined;
|
|
29
|
+
}[];
|
|
30
|
+
id: string;
|
|
31
|
+
platform: {
|
|
32
|
+
repository: "treeseed-ai/platform";
|
|
33
|
+
commit: string;
|
|
34
|
+
};
|
|
35
|
+
mutation: "approval-required";
|
|
36
|
+
environment: "production" | "staging";
|
|
37
|
+
artifacts: Record<string, {
|
|
38
|
+
digest: string;
|
|
39
|
+
source: string;
|
|
40
|
+
}>;
|
|
41
|
+
providerConnections: Partial<Record<"cloudflare" | "railway", {
|
|
42
|
+
connectionRef: string;
|
|
43
|
+
}>>;
|
|
44
|
+
};
|
|
45
|
+
}, {
|
|
46
|
+
schemaVersion: "treeseed.hosted-topology-plan/v1";
|
|
47
|
+
blockers: {
|
|
48
|
+
code: "connection-unavailable" | "dependency-cycle" | "observation-unhealthy" | "adoption-drift";
|
|
49
|
+
message: string;
|
|
50
|
+
resourceId?: string | undefined;
|
|
51
|
+
}[];
|
|
52
|
+
planDigest: string;
|
|
53
|
+
actions: {
|
|
54
|
+
action: "noop" | "create" | "adopt" | "update";
|
|
55
|
+
resourceId: string;
|
|
56
|
+
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
57
|
+
provider: "cloudflare" | "railway";
|
|
58
|
+
providerResourceId: string | null;
|
|
59
|
+
desiredDigest: string;
|
|
60
|
+
previousDigest: string | null;
|
|
61
|
+
}[];
|
|
62
|
+
environment: "production" | "staging";
|
|
63
|
+
executable: false;
|
|
64
|
+
planId: string;
|
|
65
|
+
declarationDigest: string;
|
|
66
|
+
topologyId: string;
|
|
67
|
+
platformCommit: string;
|
|
68
|
+
approvalRequired: boolean;
|
|
69
|
+
}>;
|
|
70
|
+
readonly apply: import("../control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
71
|
+
teamId: string;
|
|
72
|
+
}, {}, {
|
|
73
|
+
approval: {
|
|
74
|
+
schemaVersion: "treeseed.hosted-topology-approval/v1";
|
|
75
|
+
planDigest: string;
|
|
76
|
+
environment: "production" | "staging";
|
|
77
|
+
decision: "approved";
|
|
78
|
+
approvedBy: string;
|
|
79
|
+
approvedAt: string;
|
|
80
|
+
};
|
|
81
|
+
plan: {
|
|
82
|
+
schemaVersion: "treeseed.hosted-topology-plan/v1";
|
|
83
|
+
blockers: {
|
|
84
|
+
code: "connection-unavailable" | "dependency-cycle" | "observation-unhealthy" | "adoption-drift";
|
|
85
|
+
message: string;
|
|
86
|
+
resourceId?: string | undefined;
|
|
87
|
+
}[];
|
|
88
|
+
planDigest: string;
|
|
89
|
+
actions: {
|
|
90
|
+
action: "noop" | "create" | "adopt" | "update";
|
|
91
|
+
resourceId: string;
|
|
92
|
+
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
93
|
+
provider: "cloudflare" | "railway";
|
|
94
|
+
providerResourceId: string | null;
|
|
95
|
+
desiredDigest: string;
|
|
96
|
+
previousDigest: string | null;
|
|
97
|
+
}[];
|
|
98
|
+
environment: "production" | "staging";
|
|
99
|
+
executable: false;
|
|
100
|
+
planId: string;
|
|
101
|
+
declarationDigest: string;
|
|
102
|
+
topologyId: string;
|
|
103
|
+
platformCommit: string;
|
|
104
|
+
approvalRequired: boolean;
|
|
105
|
+
};
|
|
106
|
+
}, Record<string, unknown>>;
|
|
107
|
+
readonly status: import("../control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
108
|
+
teamId: string;
|
|
109
|
+
}, {}, undefined, {
|
|
110
|
+
operation: Record<string, unknown> | null;
|
|
111
|
+
receipt: {
|
|
112
|
+
schemaVersion: "treeseed.hosted-topology-receipt/v1";
|
|
113
|
+
resources: {
|
|
114
|
+
state: "missing" | "degraded" | "healthy";
|
|
115
|
+
resourceId: string;
|
|
116
|
+
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
117
|
+
provider: "cloudflare" | "railway";
|
|
118
|
+
providerResourceId: string | null;
|
|
119
|
+
managedBy: "external" | "treeseed" | null;
|
|
120
|
+
observedDigest: string | null;
|
|
121
|
+
observedAt: string;
|
|
122
|
+
}[];
|
|
123
|
+
planDigest: string;
|
|
124
|
+
state: "known-good";
|
|
125
|
+
environment: "production" | "staging";
|
|
126
|
+
completedAt: string;
|
|
127
|
+
receiptId: string;
|
|
128
|
+
declarationDigest: string;
|
|
129
|
+
topologyId: string;
|
|
130
|
+
platformCommit: string;
|
|
131
|
+
previousResources: {
|
|
132
|
+
state: "missing" | "degraded" | "healthy";
|
|
133
|
+
resourceId: string;
|
|
134
|
+
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
135
|
+
provider: "cloudflare" | "railway";
|
|
136
|
+
providerResourceId: string | null;
|
|
137
|
+
managedBy: "external" | "treeseed" | null;
|
|
138
|
+
observedDigest: string | null;
|
|
139
|
+
observedAt: string;
|
|
140
|
+
}[];
|
|
141
|
+
} | null;
|
|
142
|
+
}>;
|
|
143
|
+
readonly rollback: import("../control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
144
|
+
teamId: string;
|
|
145
|
+
}, {}, {
|
|
146
|
+
rollback: {
|
|
147
|
+
schemaVersion: "treeseed.hosted-topology-rollback/v1";
|
|
148
|
+
environment: "production" | "staging";
|
|
149
|
+
operations: {
|
|
150
|
+
action: "noop" | "restore" | "delete-created";
|
|
151
|
+
resourceId: string;
|
|
152
|
+
providerResourceId: string;
|
|
153
|
+
targetDigest: string | null;
|
|
154
|
+
}[];
|
|
155
|
+
rollbackId: string;
|
|
156
|
+
sourceReceiptId: string;
|
|
157
|
+
rollbackDigest: string;
|
|
158
|
+
};
|
|
159
|
+
approval: {
|
|
160
|
+
schemaVersion: "treeseed.hosted-topology-rollback-approval/v1";
|
|
161
|
+
environment: "production" | "staging";
|
|
162
|
+
decision: "approved";
|
|
163
|
+
approvedBy: string;
|
|
164
|
+
approvedAt: string;
|
|
165
|
+
rollbackDigest: string;
|
|
166
|
+
};
|
|
167
|
+
}, Record<string, unknown>>;
|
|
168
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { hostedTopologyApprovalSchema, hostedTopologyDeclarationSchema, hostedTopologyPlanSchema, hostedTopologyReceiptSchema, hostedTopologyRollbackApprovalSchema, hostedTopologyRollbackSchema } from "../../deployment/hosted-topology.js";
|
|
3
|
+
import { defineOperation } from "../operation-builder.js";
|
|
4
|
+
const teamPath = z.object({ teamId: z.string().min(1) }).strict();
|
|
5
|
+
const empty = z.object({}).strict();
|
|
6
|
+
const none = z.undefined();
|
|
7
|
+
const operationReceipt = z.record(z.unknown());
|
|
8
|
+
const HOSTED_TOPOLOGY_OPERATIONS = {
|
|
9
|
+
plan: defineOperation({
|
|
10
|
+
operationId: "infrastructure.topology.plan",
|
|
11
|
+
description: "Plan exact hosted topology reconciliation from authoritative provider observations.",
|
|
12
|
+
rest: { method: "POST", path: "/v1/teams/{teamId}/infrastructure/topology/plan" },
|
|
13
|
+
parameters: "treeseed.infrastructure.topology.plan.parameters/v1",
|
|
14
|
+
capability: "infrastructure.read",
|
|
15
|
+
authentication: "oauth",
|
|
16
|
+
oauthScopes: ["treeseed:read"],
|
|
17
|
+
kind: "read",
|
|
18
|
+
riskClass: "ordinary",
|
|
19
|
+
confirmation: "never",
|
|
20
|
+
surfaces: ["rest", "cli"],
|
|
21
|
+
cacheScope: "none",
|
|
22
|
+
pagination: "none",
|
|
23
|
+
idempotencyRequired: false
|
|
24
|
+
}, { path: teamPath, query: empty, body: z.object({ declaration: hostedTopologyDeclarationSchema }).strict(), output: hostedTopologyPlanSchema }),
|
|
25
|
+
apply: defineOperation({
|
|
26
|
+
operationId: "infrastructure.topology.apply",
|
|
27
|
+
description: "Apply an exact approved hosted topology plan through the operations runner.",
|
|
28
|
+
rest: { method: "POST", path: "/v1/teams/{teamId}/infrastructure/topology/apply" },
|
|
29
|
+
parameters: "treeseed.infrastructure.topology.apply.parameters/v1",
|
|
30
|
+
capability: "infrastructure.write",
|
|
31
|
+
authentication: "oauth",
|
|
32
|
+
oauthScopes: ["treeseed:admin"],
|
|
33
|
+
kind: "mutation",
|
|
34
|
+
riskClass: "authority",
|
|
35
|
+
confirmation: "input_required",
|
|
36
|
+
surfaces: ["rest", "cli"],
|
|
37
|
+
cacheScope: "none",
|
|
38
|
+
pagination: "none",
|
|
39
|
+
concurrencyRequired: true,
|
|
40
|
+
redactedPaths: ["body.approval.approvedBy"]
|
|
41
|
+
}, { path: teamPath, query: empty, body: z.object({ plan: hostedTopologyPlanSchema, approval: hostedTopologyApprovalSchema }).strict(), output: operationReceipt }),
|
|
42
|
+
status: defineOperation({
|
|
43
|
+
operationId: "infrastructure.topology.status",
|
|
44
|
+
description: "Read the latest authoritative hosted topology receipt and operation state.",
|
|
45
|
+
rest: { method: "GET", path: "/v1/teams/{teamId}/infrastructure/topology" },
|
|
46
|
+
parameters: "treeseed.infrastructure.topology.status.parameters/v1",
|
|
47
|
+
capability: "infrastructure.read",
|
|
48
|
+
authentication: "oauth",
|
|
49
|
+
oauthScopes: ["treeseed:read"],
|
|
50
|
+
kind: "read",
|
|
51
|
+
riskClass: "ordinary",
|
|
52
|
+
confirmation: "never",
|
|
53
|
+
surfaces: ["rest", "cli"],
|
|
54
|
+
cacheScope: "principal",
|
|
55
|
+
pagination: "none"
|
|
56
|
+
}, { path: teamPath, query: empty, body: none, output: z.object({ receipt: hostedTopologyReceiptSchema.nullable(), operation: operationReceipt.nullable() }).strict() }),
|
|
57
|
+
rollback: defineOperation({
|
|
58
|
+
operationId: "infrastructure.topology.rollback",
|
|
59
|
+
description: "Restore exact prior hosted topology lineage from a known-good receipt.",
|
|
60
|
+
rest: { method: "POST", path: "/v1/teams/{teamId}/infrastructure/topology/rollback" },
|
|
61
|
+
parameters: "treeseed.infrastructure.topology.rollback.parameters/v1",
|
|
62
|
+
capability: "infrastructure.write",
|
|
63
|
+
authentication: "oauth",
|
|
64
|
+
oauthScopes: ["treeseed:admin"],
|
|
65
|
+
kind: "mutation",
|
|
66
|
+
riskClass: "destructive",
|
|
67
|
+
confirmation: "input_required",
|
|
68
|
+
surfaces: ["rest", "cli"],
|
|
69
|
+
cacheScope: "none",
|
|
70
|
+
pagination: "none",
|
|
71
|
+
concurrencyRequired: true,
|
|
72
|
+
redactedPaths: ["body.approval.approvedBy"]
|
|
73
|
+
}, { path: teamPath, query: empty, body: z.object({ rollback: hostedTopologyRollbackSchema, approval: hostedTopologyRollbackApprovalSchema }).strict(), output: operationReceipt })
|
|
74
|
+
};
|
|
75
|
+
export {
|
|
76
|
+
HOSTED_TOPOLOGY_OPERATIONS
|
|
77
|
+
};
|
|
@@ -1716,12 +1716,12 @@ export declare const providerCommunicationNotificationAcknowledgeRequestSchema:
|
|
|
1716
1716
|
observedAt: z.ZodString;
|
|
1717
1717
|
}, "strict", z.ZodTypeAny, {
|
|
1718
1718
|
providerId: string;
|
|
1719
|
-
runnerId: string;
|
|
1720
1719
|
observedAt: string;
|
|
1720
|
+
runnerId: string;
|
|
1721
1721
|
}, {
|
|
1722
1722
|
providerId: string;
|
|
1723
|
-
runnerId: string;
|
|
1724
1723
|
observedAt: string;
|
|
1724
|
+
runnerId: string;
|
|
1725
1725
|
}>;
|
|
1726
1726
|
export declare const providerCommunicationTraceEventRequestSchema: z.ZodObject<{
|
|
1727
1727
|
leaseToken: z.ZodString;
|