@treeseed/sdk 0.13.0-rc.77 → 0.13.0-rc.80
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/agent-capacity/contracts/capacity/assignments/assignment-context-pack.d.ts +6 -6
- package/dist/capacity-provider/capability-ontology.d.ts +2 -2
- package/dist/deployment/hosted-topology-template.d.ts +449 -0
- package/dist/deployment/hosted-topology-template.js +56 -0
- package/dist/deployment/hosted-topology.d.ts +1143 -76
- package/dist/deployment/hosted-topology.js +148 -22
- package/dist/deployment/index.d.ts +1 -0
- package/dist/deployment/index.js +1 -0
- package/dist/operator-contracts/canonical-command-tree.js +1 -1
- package/dist/operator-contracts/catalog/hosted-topology-operations.d.ts +102 -9
- package/dist/operator-contracts/control-plane-operations.d.ts +102 -9
- package/package.json +2 -1
|
@@ -1,12 +1,45 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { deploymentDigest } from "./canonical.js";
|
|
3
3
|
const identifier = z.string().regex(/^[a-z][a-z0-9.-]{1,63}$/u);
|
|
4
|
+
const custodyIdentifier = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u);
|
|
4
5
|
const digest = z.string().regex(/^sha256:[a-f0-9]{64}$/u);
|
|
5
6
|
const gitCommit = z.string().regex(/^[a-f0-9]{40}$/u);
|
|
6
7
|
const timestamp = z.string().datetime();
|
|
8
|
+
const hostedEnvironmentSchema = z.enum(["staging", "production"]);
|
|
9
|
+
function hostedTopologyStateKey(input) {
|
|
10
|
+
const teamId = custodyIdentifier.parse(input.teamId), deploymentId = custodyIdentifier.parse(input.deploymentId), stackId = custodyIdentifier.parse(input.stackId);
|
|
11
|
+
const environment = hostedEnvironmentSchema.parse(input.environment);
|
|
12
|
+
return `teams/${teamId}/opentofu/v1/deployments/${deploymentId}/environments/${environment}/stacks/${stackId}/terraform.tfstate`;
|
|
13
|
+
}
|
|
14
|
+
const hostedStateBackendCoreSchema = z.object({
|
|
15
|
+
schemaVersion: z.literal("treeseed.hosted-state-backend/v1"),
|
|
16
|
+
type: z.literal("s3"),
|
|
17
|
+
teamId: custodyIdentifier,
|
|
18
|
+
deploymentId: custodyIdentifier,
|
|
19
|
+
environment: hostedEnvironmentSchema,
|
|
20
|
+
stackId: custodyIdentifier,
|
|
21
|
+
connectionRef: identifier,
|
|
22
|
+
bucket: z.string().trim().min(3).max(255),
|
|
23
|
+
key: z.string().trim().min(1).max(1024),
|
|
24
|
+
region: z.string().trim().min(1).max(128),
|
|
25
|
+
endpoint: z.string().url().optional(),
|
|
26
|
+
usePathStyle: z.boolean().optional(),
|
|
27
|
+
encryptionKeyRef: custodyIdentifier
|
|
28
|
+
}).strict();
|
|
29
|
+
const hostedStateBackendSchema = hostedStateBackendCoreSchema.extend({ bindingDigest: digest }).strict().superRefine((backend, context) => {
|
|
30
|
+
const { bindingDigest: _bindingDigest, ...core } = backend;
|
|
31
|
+
if (backend.key !== hostedTopologyStateKey(backend)) context.addIssue({ code: z.ZodIssueCode.custom, path: ["key"], message: "Hosted state backend key must be the canonical team deployment stack key." });
|
|
32
|
+
if (deploymentDigest(core) !== backend.bindingDigest) context.addIssue({ code: z.ZodIssueCode.custom, path: ["bindingDigest"], message: "Hosted state backend digest must bind its complete custody and storage identity." });
|
|
33
|
+
});
|
|
34
|
+
function bindHostedStateBackend(input) {
|
|
35
|
+
const core = hostedStateBackendCoreSchema.parse(input);
|
|
36
|
+
if (core.key !== hostedTopologyStateKey(core)) throw new Error("Hosted state backend key must be the canonical team deployment stack key.");
|
|
37
|
+
return hostedStateBackendSchema.parse({ ...core, bindingDigest: deploymentDigest(core) });
|
|
38
|
+
}
|
|
7
39
|
const hostedProviderSchema = z.enum(["cloudflare", "railway"]);
|
|
8
40
|
const hostedResourceKindSchema = z.enum([
|
|
9
41
|
"admin-application",
|
|
42
|
+
"pages-application",
|
|
10
43
|
"dns-record",
|
|
11
44
|
"tls-policy",
|
|
12
45
|
"api-proxy",
|
|
@@ -15,14 +48,21 @@ const hostedResourceKindSchema = z.enum([
|
|
|
15
48
|
"operations-runner",
|
|
16
49
|
"treedx-service"
|
|
17
50
|
]);
|
|
51
|
+
const hostedArchiveArtifactSchema = z.object({ kind: z.literal("archive"), format: z.literal("tar+gzip"), digest, source: z.string().url() }).strict();
|
|
52
|
+
const hostedFileArtifactSchema = z.object({ kind: z.literal("file"), mediaType: z.string().trim().min(1).max(255), digest, source: z.string().url() }).strict();
|
|
53
|
+
const hostedOciArtifactSchema = z.object({ kind: z.literal("oci-image"), digest, identity: z.string().regex(/^[a-z0-9][a-z0-9._/-]*@sha256:[a-f0-9]{64}$/u) }).strict().superRefine((artifact, context) => {
|
|
54
|
+
if (!artifact.identity.endsWith(`@${artifact.digest}`)) context.addIssue({ code: z.ZodIssueCode.custom, path: ["identity"], message: "Hosted OCI identity must bind the declared digest." });
|
|
55
|
+
});
|
|
56
|
+
const hostedArtifactSchema = z.union([hostedArchiveArtifactSchema, hostedFileArtifactSchema, hostedOciArtifactSchema]);
|
|
18
57
|
const parameterSchema = z.union([
|
|
19
58
|
z.object({ input: identifier }).strict(),
|
|
20
59
|
z.object({ artifact: identifier }).strict(),
|
|
21
60
|
z.object({ resourceOutput: z.object({ resourceId: identifier, output: identifier }).strict() }).strict(),
|
|
22
61
|
z.object({ literal: z.union([z.string().max(4096), z.number().finite(), z.boolean()]) }).strict()
|
|
23
62
|
]);
|
|
63
|
+
const parameterName = z.union([identifier, z.string().regex(/^variable\.[A-Z][A-Z0-9_]{1,127}$/u)]);
|
|
24
64
|
const resourceProviderKinds = {
|
|
25
|
-
cloudflare: /* @__PURE__ */ new Set(["admin-application", "dns-record", "tls-policy", "api-proxy"]),
|
|
65
|
+
cloudflare: /* @__PURE__ */ new Set(["admin-application", "pages-application", "dns-record", "tls-policy", "api-proxy"]),
|
|
26
66
|
railway: /* @__PURE__ */ new Set(["control-plane-api", "postgresql", "operations-runner", "treedx-service"])
|
|
27
67
|
};
|
|
28
68
|
const sensitiveKey = /(?:credential|password|private.?key|registration.?code|secret|token)/iu;
|
|
@@ -41,23 +81,31 @@ const hostedResourceDeclarationSchema = z.object({
|
|
|
41
81
|
provider: hostedProviderSchema,
|
|
42
82
|
kind: hostedResourceKindSchema,
|
|
43
83
|
dependsOn: z.array(identifier).default([]),
|
|
44
|
-
parameters: z.record(
|
|
84
|
+
parameters: z.record(parameterName, parameterSchema).default({}),
|
|
45
85
|
adoption: z.object({ mode: z.literal("adopt-or-create"), externalIdInput: identifier.optional(), replacement: z.literal("forbidden") }).strict()
|
|
46
86
|
}).strict();
|
|
47
87
|
const hostedTopologyDeclarationSchema = z.object({
|
|
48
88
|
schemaVersion: z.literal("treeseed.hosted-topology/v1"),
|
|
49
89
|
id: identifier,
|
|
50
|
-
|
|
90
|
+
teamId: custodyIdentifier,
|
|
91
|
+
deploymentId: custodyIdentifier,
|
|
92
|
+
stackId: custodyIdentifier,
|
|
93
|
+
environment: hostedEnvironmentSchema,
|
|
51
94
|
mutation: z.literal("approval-required"),
|
|
52
95
|
platform: z.object({ repository: z.literal("treeseed-ai/platform"), commit: gitCommit }).strict(),
|
|
96
|
+
stateBackend: z.object({ connectionRef: identifier }).strict(),
|
|
53
97
|
providerConnections: z.record(hostedProviderSchema, z.object({ connectionRef: identifier }).strict()),
|
|
54
|
-
artifacts: z.record(identifier,
|
|
55
|
-
resources: z.array(hostedResourceDeclarationSchema)
|
|
98
|
+
artifacts: z.record(identifier, hostedArtifactSchema),
|
|
99
|
+
resources: z.array(hostedResourceDeclarationSchema)
|
|
56
100
|
}).strict().superRefine((declaration, context) => {
|
|
57
101
|
const ids = declaration.resources.map(({ id }) => id);
|
|
58
102
|
if (new Set(ids).size !== ids.length) context.addIssue({ code: z.ZodIssueCode.custom, path: ["resources"], message: "Hosted topology resource identities must be unique." });
|
|
59
103
|
for (const [index, resource] of declaration.resources.entries()) {
|
|
60
104
|
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}.` });
|
|
105
|
+
if (resource.kind === "pages-application") {
|
|
106
|
+
for (const key of ["artifact", "artifact-format", "name", "production-branch", "destination-dir"])
|
|
107
|
+
if (!resource.parameters[key]) context.addIssue({ code: z.ZodIssueCode.custom, path: ["resources", index, "parameters", key], message: `Cloudflare Pages applications require ${key}.` });
|
|
108
|
+
}
|
|
61
109
|
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}.` });
|
|
62
110
|
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." });
|
|
63
111
|
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." });
|
|
@@ -79,9 +127,13 @@ const hostedTopologyPlanShape = {
|
|
|
79
127
|
planDigest: digest,
|
|
80
128
|
declarationDigest: digest,
|
|
81
129
|
topologyId: identifier,
|
|
82
|
-
|
|
130
|
+
teamId: custodyIdentifier,
|
|
131
|
+
deploymentId: custodyIdentifier,
|
|
132
|
+
stackId: custodyIdentifier,
|
|
133
|
+
environment: hostedEnvironmentSchema,
|
|
83
134
|
platformCommit: gitCommit,
|
|
84
|
-
|
|
135
|
+
stateBackend: hostedStateBackendSchema.nullable(),
|
|
136
|
+
artifacts: z.record(identifier, hostedArtifactSchema),
|
|
85
137
|
providerConnections: z.record(hostedProviderSchema, hostedConnectionSnapshotSchema),
|
|
86
138
|
actions: z.array(z.object({
|
|
87
139
|
resourceId: identifier,
|
|
@@ -93,7 +145,7 @@ const hostedTopologyPlanShape = {
|
|
|
93
145
|
previousDigest: digest.nullable(),
|
|
94
146
|
providerResourceId: z.string().min(1).max(512).nullable()
|
|
95
147
|
}).strict()),
|
|
96
|
-
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()),
|
|
148
|
+
blockers: z.array(z.object({ code: z.enum(["connection-unavailable", "state-backend-unavailable", "dependency-cycle", "observation-unhealthy", "adoption-drift"]), resourceId: identifier.optional(), message: z.string().min(1) }).strict()),
|
|
97
149
|
approvalRequired: z.boolean(),
|
|
98
150
|
executable: z.literal(false)
|
|
99
151
|
};
|
|
@@ -107,10 +159,16 @@ function verifyPlanBinding(plan, context) {
|
|
|
107
159
|
if (!plan.providerConnections[action.provider] && !plan.blockers.some((blocker) => blocker.code === "connection-unavailable"))
|
|
108
160
|
context.addIssue({ code: z.ZodIssueCode.custom, path: ["providerConnections", action.provider], message: `Hosted plan is missing the selected ${action.provider} connection snapshot.` });
|
|
109
161
|
}
|
|
162
|
+
if (plan.stateBackend && [plan.teamId !== plan.stateBackend.teamId, plan.deploymentId !== plan.stateBackend.deploymentId, plan.stackId !== plan.stateBackend.stackId, plan.environment !== plan.stateBackend.environment].some(Boolean))
|
|
163
|
+
context.addIssue({ code: z.ZodIssueCode.custom, path: ["stateBackend"], message: "Hosted topology plan and state backend custody identities must match." });
|
|
110
164
|
const core = {
|
|
111
165
|
declarationDigest: plan.declarationDigest,
|
|
112
166
|
topologyId: plan.topologyId,
|
|
167
|
+
teamId: plan.teamId,
|
|
168
|
+
deploymentId: plan.deploymentId,
|
|
169
|
+
stackId: plan.stackId,
|
|
113
170
|
environment: plan.environment,
|
|
171
|
+
stateBackend: plan.stateBackend,
|
|
114
172
|
platformCommit: plan.platformCommit,
|
|
115
173
|
artifacts: plan.artifacts,
|
|
116
174
|
providerConnections: plan.providerConnections,
|
|
@@ -125,7 +183,11 @@ const hostedTopologyPlanSchema = z.object(hostedTopologyPlanShape).strict().supe
|
|
|
125
183
|
const hostedTopologyApprovalSchema = z.object({
|
|
126
184
|
schemaVersion: z.literal("treeseed.hosted-topology-approval/v1"),
|
|
127
185
|
planDigest: digest,
|
|
128
|
-
|
|
186
|
+
teamId: custodyIdentifier,
|
|
187
|
+
deploymentId: custodyIdentifier,
|
|
188
|
+
stackId: custodyIdentifier,
|
|
189
|
+
environment: hostedEnvironmentSchema,
|
|
190
|
+
backendBindingDigest: digest,
|
|
129
191
|
decision: z.literal("approved"),
|
|
130
192
|
approvedBy: z.string().min(1).max(256),
|
|
131
193
|
approvedAt: timestamp
|
|
@@ -141,7 +203,11 @@ const hostedTopologyReceiptSchema = z.object({
|
|
|
141
203
|
planDigest: digest,
|
|
142
204
|
declarationDigest: digest,
|
|
143
205
|
topologyId: identifier,
|
|
144
|
-
|
|
206
|
+
teamId: custodyIdentifier,
|
|
207
|
+
deploymentId: custodyIdentifier,
|
|
208
|
+
stackId: custodyIdentifier,
|
|
209
|
+
environment: hostedEnvironmentSchema,
|
|
210
|
+
backendBindingDigest: digest,
|
|
145
211
|
platformCommit: gitCommit,
|
|
146
212
|
resources: z.array(hostedResourceObservationSchema),
|
|
147
213
|
previousResources: z.array(hostedResourceObservationSchema),
|
|
@@ -152,7 +218,11 @@ const hostedTopologyRollbackSchema = z.object({
|
|
|
152
218
|
schemaVersion: z.literal("treeseed.hosted-topology-rollback/v1"),
|
|
153
219
|
rollbackId: z.string().regex(/^topology-rollback-[a-f0-9]{16}$/u),
|
|
154
220
|
sourceReceiptId: z.string().regex(/^topology-receipt-[a-f0-9]{16}$/u),
|
|
155
|
-
|
|
221
|
+
teamId: custodyIdentifier,
|
|
222
|
+
deploymentId: custodyIdentifier,
|
|
223
|
+
stackId: custodyIdentifier,
|
|
224
|
+
environment: hostedEnvironmentSchema,
|
|
225
|
+
backendBindingDigest: digest,
|
|
156
226
|
operations: z.array(z.object({
|
|
157
227
|
resourceId: identifier,
|
|
158
228
|
action: z.enum(["restore", "delete-created", "noop"]),
|
|
@@ -164,7 +234,11 @@ const hostedTopologyRollbackSchema = z.object({
|
|
|
164
234
|
const hostedTopologyRollbackApprovalSchema = z.object({
|
|
165
235
|
schemaVersion: z.literal("treeseed.hosted-topology-rollback-approval/v1"),
|
|
166
236
|
rollbackDigest: digest,
|
|
167
|
-
|
|
237
|
+
teamId: custodyIdentifier,
|
|
238
|
+
deploymentId: custodyIdentifier,
|
|
239
|
+
stackId: custodyIdentifier,
|
|
240
|
+
environment: hostedEnvironmentSchema,
|
|
241
|
+
backendBindingDigest: digest,
|
|
168
242
|
decision: z.literal("approved"),
|
|
169
243
|
approvedBy: z.string().min(1).max(256),
|
|
170
244
|
approvedAt: timestamp
|
|
@@ -174,7 +248,11 @@ const hostedTopologyRollbackExecutionSchema = z.object({
|
|
|
174
248
|
rollback: hostedTopologyRollbackSchema,
|
|
175
249
|
sourceReceiptId: z.string().regex(/^topology-receipt-[a-f0-9]{16}$/u),
|
|
176
250
|
topologyId: identifier,
|
|
177
|
-
|
|
251
|
+
teamId: custodyIdentifier,
|
|
252
|
+
deploymentId: custodyIdentifier,
|
|
253
|
+
stackId: custodyIdentifier,
|
|
254
|
+
environment: hostedEnvironmentSchema,
|
|
255
|
+
backendBindingDigest: digest,
|
|
178
256
|
sourcePlanDigest: digest,
|
|
179
257
|
targetPlanDigest: digest,
|
|
180
258
|
executionDigest: digest
|
|
@@ -183,7 +261,11 @@ const hostedTopologyRollbackExecutionSchema = z.object({
|
|
|
183
261
|
rollback: execution.rollback,
|
|
184
262
|
sourceReceiptId: execution.sourceReceiptId,
|
|
185
263
|
topologyId: execution.topologyId,
|
|
264
|
+
teamId: execution.teamId,
|
|
265
|
+
deploymentId: execution.deploymentId,
|
|
266
|
+
stackId: execution.stackId,
|
|
186
267
|
environment: execution.environment,
|
|
268
|
+
backendBindingDigest: execution.backendBindingDigest,
|
|
187
269
|
sourcePlanDigest: execution.sourcePlanDigest,
|
|
188
270
|
targetPlanDigest: execution.targetPlanDigest
|
|
189
271
|
};
|
|
@@ -192,7 +274,11 @@ const hostedTopologyRollbackExecutionSchema = z.object({
|
|
|
192
274
|
const hostedTopologyRollbackExecutionApprovalSchema = z.object({
|
|
193
275
|
schemaVersion: z.literal("treeseed.hosted-topology-rollback-execution-approval/v1"),
|
|
194
276
|
executionDigest: digest,
|
|
195
|
-
|
|
277
|
+
teamId: custodyIdentifier,
|
|
278
|
+
deploymentId: custodyIdentifier,
|
|
279
|
+
stackId: custodyIdentifier,
|
|
280
|
+
environment: hostedEnvironmentSchema,
|
|
281
|
+
backendBindingDigest: digest,
|
|
196
282
|
decision: z.literal("approved"),
|
|
197
283
|
approvedBy: z.string().min(1).max(256),
|
|
198
284
|
approvedAt: timestamp
|
|
@@ -237,6 +323,14 @@ function planHostedTopology(input) {
|
|
|
237
323
|
};
|
|
238
324
|
const observations = observationMap(input.observations, normalizedDeclaration);
|
|
239
325
|
const blockers = [];
|
|
326
|
+
let stateBackend = null;
|
|
327
|
+
if (!input.stateBackend) blockers.push({ code: "state-backend-unavailable", message: `State backend connection ${normalizedDeclaration.stateBackend.connectionRef} is unavailable.` });
|
|
328
|
+
else {
|
|
329
|
+
stateBackend = hostedStateBackendSchema.parse(input.stateBackend);
|
|
330
|
+
if (stateBackend.connectionRef !== normalizedDeclaration.stateBackend.connectionRef) blockers.push({ code: "state-backend-unavailable", message: `State backend connection ${normalizedDeclaration.stateBackend.connectionRef} is unavailable.` });
|
|
331
|
+
if (stateBackend.teamId !== normalizedDeclaration.teamId || stateBackend.deploymentId !== normalizedDeclaration.deploymentId || stateBackend.stackId !== normalizedDeclaration.stackId || stateBackend.environment !== normalizedDeclaration.environment)
|
|
332
|
+
throw new Error("Hosted state backend custody identity does not match the topology declaration.");
|
|
333
|
+
}
|
|
240
334
|
const providerConnections = {};
|
|
241
335
|
for (const [provider, binding] of Object.entries(normalizedDeclaration.providerConnections)) {
|
|
242
336
|
const snapshot = input.connections[provider];
|
|
@@ -269,7 +363,11 @@ function planHostedTopology(input) {
|
|
|
269
363
|
const core = {
|
|
270
364
|
declarationDigest,
|
|
271
365
|
topologyId: declaration.id,
|
|
366
|
+
teamId: declaration.teamId,
|
|
367
|
+
deploymentId: declaration.deploymentId,
|
|
368
|
+
stackId: declaration.stackId,
|
|
272
369
|
environment: declaration.environment,
|
|
370
|
+
stateBackend,
|
|
273
371
|
platformCommit: declaration.platform.commit,
|
|
274
372
|
artifacts: normalizedDeclaration.artifacts,
|
|
275
373
|
providerConnections,
|
|
@@ -282,9 +380,10 @@ function planHostedTopology(input) {
|
|
|
282
380
|
function authorizeHostedTopologyPlan(planInput, approvalInput) {
|
|
283
381
|
const plan = hostedTopologyPlanSchema.parse(planInput);
|
|
284
382
|
if (plan.blockers.length) throw new Error("Hosted topology plan has unresolved blockers.");
|
|
383
|
+
if (!plan.stateBackend) throw new Error("Hosted topology plan has no state backend authority.");
|
|
285
384
|
const approval = approvalInput ? hostedTopologyApprovalSchema.parse(approvalInput) : null;
|
|
286
385
|
if (plan.approvalRequired && !approval) throw new Error("Hosted topology mutation requires environment approval.");
|
|
287
|
-
if (approval && (approval.planDigest !== plan.planDigest || approval.environment !== plan.environment)) throw new Error("Hosted topology approval does not bind the exact plan and
|
|
386
|
+
if (approval && (approval.planDigest !== plan.planDigest || approval.teamId !== plan.teamId || approval.deploymentId !== plan.deploymentId || approval.stackId !== plan.stackId || approval.environment !== plan.environment || approval.backendBindingDigest !== plan.stateBackend.bindingDigest)) throw new Error("Hosted topology approval does not bind the exact plan custody and backend.");
|
|
288
387
|
const { approvalRequired: _approvalRequired, ...approvedPlan } = plan;
|
|
289
388
|
return authorizedHostedTopologyPlanSchema.parse({ ...approvedPlan, executable: true, approval });
|
|
290
389
|
}
|
|
@@ -300,7 +399,23 @@ function verifyHostedTopologyReadback(input) {
|
|
|
300
399
|
}
|
|
301
400
|
const completedAt = timestamp.parse(input.completedAt);
|
|
302
401
|
const receiptDigest = deploymentDigest({ planDigest: plan.planDigest, resources, completedAt });
|
|
303
|
-
return hostedTopologyReceiptSchema.parse({
|
|
402
|
+
return hostedTopologyReceiptSchema.parse({
|
|
403
|
+
schemaVersion: "treeseed.hosted-topology-receipt/v1",
|
|
404
|
+
receiptId: `topology-receipt-${receiptDigest.slice(7, 23)}`,
|
|
405
|
+
planDigest: plan.planDigest,
|
|
406
|
+
declarationDigest: plan.declarationDigest,
|
|
407
|
+
topologyId: plan.topologyId,
|
|
408
|
+
teamId: plan.teamId,
|
|
409
|
+
deploymentId: plan.deploymentId,
|
|
410
|
+
stackId: plan.stackId,
|
|
411
|
+
environment: plan.environment,
|
|
412
|
+
backendBindingDigest: plan.stateBackend.bindingDigest,
|
|
413
|
+
platformCommit: plan.platformCommit,
|
|
414
|
+
resources,
|
|
415
|
+
previousResources: input.previousResources.map((item) => hostedResourceObservationSchema.parse(item)).sort((left, right) => left.resourceId.localeCompare(right.resourceId)),
|
|
416
|
+
state: "known-good",
|
|
417
|
+
completedAt
|
|
418
|
+
});
|
|
304
419
|
}
|
|
305
420
|
function planHostedTopologyRollback(receiptInput) {
|
|
306
421
|
const receipt = hostedTopologyReceiptSchema.parse(receiptInput);
|
|
@@ -309,12 +424,13 @@ function planHostedTopologyRollback(receiptInput) {
|
|
|
309
424
|
const prior = previous.get(resource.resourceId);
|
|
310
425
|
return { resourceId: resource.resourceId, action: !prior || prior.state === "missing" ? "delete-created" : prior.observedDigest === resource.observedDigest ? "noop" : "restore", providerResourceId: resource.providerResourceId, targetDigest: prior?.observedDigest ?? null };
|
|
311
426
|
}).sort((left, right) => left.resourceId.localeCompare(right.resourceId));
|
|
312
|
-
const
|
|
313
|
-
|
|
427
|
+
const custody = { teamId: receipt.teamId, deploymentId: receipt.deploymentId, stackId: receipt.stackId, environment: receipt.environment, backendBindingDigest: receipt.backendBindingDigest };
|
|
428
|
+
const rollbackDigest = deploymentDigest({ sourceReceiptId: receipt.receiptId, ...custody, operations });
|
|
429
|
+
return hostedTopologyRollbackSchema.parse({ schemaVersion: "treeseed.hosted-topology-rollback/v1", rollbackId: `topology-rollback-${rollbackDigest.slice(7, 23)}`, sourceReceiptId: receipt.receiptId, ...custody, operations, rollbackDigest });
|
|
314
430
|
}
|
|
315
431
|
function authorizeHostedTopologyRollback(rollbackInput, approvalInput) {
|
|
316
432
|
const rollback = hostedTopologyRollbackSchema.parse(rollbackInput), approval = hostedTopologyRollbackApprovalSchema.parse(approvalInput);
|
|
317
|
-
if (approval.rollbackDigest !== rollback.rollbackDigest || approval.environment !== rollback.environment) throw new Error("Hosted topology rollback approval does not bind the exact rollback and
|
|
433
|
+
if (approval.rollbackDigest !== rollback.rollbackDigest || approval.teamId !== rollback.teamId || approval.deploymentId !== rollback.deploymentId || approval.stackId !== rollback.stackId || approval.environment !== rollback.environment || approval.backendBindingDigest !== rollback.backendBindingDigest) throw new Error("Hosted topology rollback approval does not bind the exact rollback custody and backend.");
|
|
318
434
|
return { rollback, approval };
|
|
319
435
|
}
|
|
320
436
|
function planHostedTopologyRollbackExecution(input) {
|
|
@@ -322,7 +438,12 @@ function planHostedTopologyRollbackExecution(input) {
|
|
|
322
438
|
const sourcePlan = input.sourcePlan && typeof input.sourcePlan === "object" && "approval" in input.sourcePlan ? authorizedHostedTopologyPlanSchema.parse(input.sourcePlan) : hostedTopologyPlanSchema.parse(input.sourcePlan);
|
|
323
439
|
const targetPlan = hostedTopologyPlanSchema.parse(input.targetPlan);
|
|
324
440
|
if (sourceReceipt.receiptId !== rollback.sourceReceiptId || sourceReceipt.planDigest !== sourcePlan.planDigest) throw new Error("Hosted rollback execution source receipt is stale.");
|
|
325
|
-
|
|
441
|
+
const custody = { teamId: rollback.teamId, deploymentId: rollback.deploymentId, stackId: rollback.stackId, environment: rollback.environment, backendBindingDigest: rollback.backendBindingDigest };
|
|
442
|
+
for (const candidate of [sourceReceipt, sourcePlan, targetPlan]) {
|
|
443
|
+
if (candidate.teamId !== custody.teamId || candidate.deploymentId !== custody.deploymentId || candidate.stackId !== custody.stackId || candidate.environment !== custody.environment) throw new Error("Hosted rollback execution custody identity does not match its source and target plans.");
|
|
444
|
+
const backendDigest = "backendBindingDigest" in candidate ? candidate.backendBindingDigest : candidate.stateBackend?.bindingDigest;
|
|
445
|
+
if (backendDigest !== custody.backendBindingDigest) throw new Error("Hosted rollback execution state backend changed.");
|
|
446
|
+
}
|
|
326
447
|
if (sourceReceipt.topologyId !== sourcePlan.topologyId || sourcePlan.topologyId !== targetPlan.topologyId) throw new Error("Hosted rollback execution topology identity changed.");
|
|
327
448
|
const sourceActions = new Map(sourcePlan.actions.map((action) => [action.resourceId, action]));
|
|
328
449
|
const sourceResources = new Map(sourceReceipt.resources.map((resource) => [resource.resourceId, resource]));
|
|
@@ -342,7 +463,7 @@ function planHostedTopologyRollbackExecution(input) {
|
|
|
342
463
|
rollback,
|
|
343
464
|
sourceReceiptId: sourceReceipt.receiptId,
|
|
344
465
|
topologyId: sourceReceipt.topologyId,
|
|
345
|
-
|
|
466
|
+
...custody,
|
|
346
467
|
sourcePlanDigest: sourcePlan.planDigest,
|
|
347
468
|
targetPlanDigest: targetPlan.planDigest
|
|
348
469
|
};
|
|
@@ -350,7 +471,7 @@ function planHostedTopologyRollbackExecution(input) {
|
|
|
350
471
|
}
|
|
351
472
|
function authorizeHostedTopologyRollbackExecution(executionInput, approvalInput) {
|
|
352
473
|
const execution = hostedTopologyRollbackExecutionSchema.parse(executionInput), approval = hostedTopologyRollbackExecutionApprovalSchema.parse(approvalInput);
|
|
353
|
-
if (approval.executionDigest !== execution.executionDigest || approval.environment !== execution.environment) throw new Error("Hosted rollback execution approval does not bind the exact source
|
|
474
|
+
if (approval.executionDigest !== execution.executionDigest || approval.teamId !== execution.teamId || approval.deploymentId !== execution.deploymentId || approval.stackId !== execution.stackId || approval.environment !== execution.environment || approval.backendBindingDigest !== execution.backendBindingDigest) throw new Error("Hosted rollback execution approval does not bind the exact source target custody and backend.");
|
|
354
475
|
return { execution, approval };
|
|
355
476
|
}
|
|
356
477
|
export {
|
|
@@ -358,9 +479,13 @@ export {
|
|
|
358
479
|
authorizeHostedTopologyRollback,
|
|
359
480
|
authorizeHostedTopologyRollbackExecution,
|
|
360
481
|
authorizedHostedTopologyPlanSchema,
|
|
482
|
+
bindHostedStateBackend,
|
|
483
|
+
hostedArtifactSchema,
|
|
361
484
|
hostedProviderSchema,
|
|
485
|
+
hostedResourceDeclarationSchema,
|
|
362
486
|
hostedResourceKindSchema,
|
|
363
487
|
hostedResourceObservationSchema,
|
|
488
|
+
hostedStateBackendSchema,
|
|
364
489
|
hostedTopologyApprovalSchema,
|
|
365
490
|
hostedTopologyDeclarationSchema,
|
|
366
491
|
hostedTopologyPlanSchema,
|
|
@@ -369,6 +494,7 @@ export {
|
|
|
369
494
|
hostedTopologyRollbackExecutionApprovalSchema,
|
|
370
495
|
hostedTopologyRollbackExecutionSchema,
|
|
371
496
|
hostedTopologyRollbackSchema,
|
|
497
|
+
hostedTopologyStateKey,
|
|
372
498
|
planHostedTopology,
|
|
373
499
|
planHostedTopologyRollback,
|
|
374
500
|
planHostedTopologyRollbackExecution,
|
package/dist/deployment/index.js
CHANGED
|
@@ -364,7 +364,7 @@ const commandTree = {
|
|
|
364
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
365
|
]),
|
|
366
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") },
|
|
367
|
+
{ nodeType: "leaf", segment: "plan", description: "Plan Cloudflare and Railway reconciliation from a portable hosted-topology declaration or template.", kind: "read", arguments: [{ name: "file", description: "Hosted topology YAML or JSON declaration or template.", required: true }], options: [{ name: "--artifacts", description: "Exact runtime artifact-input YAML or JSON required by a portable template.", type: "string" }], resultSchemaId: "treeseed.hosted-topology-plan/v1", execution: local("local.platform.topology.plan") },
|
|
368
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
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
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") }
|
|
@@ -6,7 +6,7 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
6
6
|
schemaVersion: "treeseed.hosted-topology/v1";
|
|
7
7
|
resources: {
|
|
8
8
|
id: string;
|
|
9
|
-
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
9
|
+
kind: "admin-application" | "pages-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
10
10
|
provider: "cloudflare" | "railway";
|
|
11
11
|
adoption: {
|
|
12
12
|
mode: "adopt-or-create";
|
|
@@ -28,6 +28,7 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
28
28
|
}> | undefined;
|
|
29
29
|
}[];
|
|
30
30
|
id: string;
|
|
31
|
+
teamId: string;
|
|
31
32
|
platform: {
|
|
32
33
|
repository: "treeseed-ai/platform";
|
|
33
34
|
commit: string;
|
|
@@ -37,7 +38,23 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
37
38
|
artifacts: Record<string, {
|
|
38
39
|
digest: string;
|
|
39
40
|
source: string;
|
|
41
|
+
kind: "archive";
|
|
42
|
+
format: "tar+gzip";
|
|
43
|
+
} | {
|
|
44
|
+
digest: string;
|
|
45
|
+
source: string;
|
|
46
|
+
kind: "file";
|
|
47
|
+
mediaType: string;
|
|
48
|
+
} | {
|
|
49
|
+
digest: string;
|
|
50
|
+
kind: "oci-image";
|
|
51
|
+
identity: string;
|
|
40
52
|
}>;
|
|
53
|
+
deploymentId: string;
|
|
54
|
+
stackId: string;
|
|
55
|
+
stateBackend: {
|
|
56
|
+
connectionRef: string;
|
|
57
|
+
};
|
|
41
58
|
providerConnections: Partial<Record<"cloudflare" | "railway", {
|
|
42
59
|
connectionRef: string;
|
|
43
60
|
}>>;
|
|
@@ -45,7 +62,7 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
45
62
|
}, {
|
|
46
63
|
schemaVersion: "treeseed.hosted-topology-plan/v1";
|
|
47
64
|
blockers: {
|
|
48
|
-
code: "connection-unavailable" | "dependency-cycle" | "observation-unhealthy" | "adoption-drift";
|
|
65
|
+
code: "connection-unavailable" | "state-backend-unavailable" | "dependency-cycle" | "observation-unhealthy" | "adoption-drift";
|
|
49
66
|
message: string;
|
|
50
67
|
resourceId?: string | undefined;
|
|
51
68
|
}[];
|
|
@@ -53,12 +70,12 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
53
70
|
actions: {
|
|
54
71
|
action: "noop" | "create" | "adopt" | "update";
|
|
55
72
|
resourceId: string;
|
|
56
|
-
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
73
|
+
kind: "admin-application" | "pages-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
57
74
|
provider: "cloudflare" | "railway";
|
|
58
75
|
providerResourceId: string | null;
|
|
59
76
|
desiredResource: {
|
|
60
77
|
id: string;
|
|
61
|
-
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
78
|
+
kind: "admin-application" | "pages-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
62
79
|
provider: "cloudflare" | "railway";
|
|
63
80
|
adoption: {
|
|
64
81
|
mode: "adopt-or-create";
|
|
@@ -82,13 +99,43 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
82
99
|
desiredDigest: string;
|
|
83
100
|
previousDigest: string | null;
|
|
84
101
|
}[];
|
|
102
|
+
teamId: string;
|
|
85
103
|
environment: "production" | "staging";
|
|
86
104
|
executable: false;
|
|
87
105
|
artifacts: Record<string, {
|
|
88
106
|
digest: string;
|
|
89
107
|
source: string;
|
|
108
|
+
kind: "archive";
|
|
109
|
+
format: "tar+gzip";
|
|
110
|
+
} | {
|
|
111
|
+
digest: string;
|
|
112
|
+
source: string;
|
|
113
|
+
kind: "file";
|
|
114
|
+
mediaType: string;
|
|
115
|
+
} | {
|
|
116
|
+
digest: string;
|
|
117
|
+
kind: "oci-image";
|
|
118
|
+
identity: string;
|
|
90
119
|
}>;
|
|
91
120
|
planId: string;
|
|
121
|
+
deploymentId: string;
|
|
122
|
+
stackId: string;
|
|
123
|
+
stateBackend: {
|
|
124
|
+
key: string;
|
|
125
|
+
type: "s3";
|
|
126
|
+
schemaVersion: "treeseed.hosted-state-backend/v1";
|
|
127
|
+
teamId: string;
|
|
128
|
+
environment: "production" | "staging";
|
|
129
|
+
deploymentId: string;
|
|
130
|
+
stackId: string;
|
|
131
|
+
connectionRef: string;
|
|
132
|
+
bucket: string;
|
|
133
|
+
region: string;
|
|
134
|
+
encryptionKeyRef: string;
|
|
135
|
+
bindingDigest: string;
|
|
136
|
+
endpoint?: string | undefined;
|
|
137
|
+
usePathStyle?: boolean | undefined;
|
|
138
|
+
} | null;
|
|
92
139
|
providerConnections: Partial<Record<"cloudflare" | "railway", {
|
|
93
140
|
connectionRef: string;
|
|
94
141
|
nonSecretConfig: Record<string, string | number | boolean>;
|
|
@@ -104,7 +151,11 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
104
151
|
approval: {
|
|
105
152
|
schemaVersion: "treeseed.hosted-topology-approval/v1";
|
|
106
153
|
planDigest: string;
|
|
154
|
+
teamId: string;
|
|
107
155
|
environment: "production" | "staging";
|
|
156
|
+
deploymentId: string;
|
|
157
|
+
stackId: string;
|
|
158
|
+
backendBindingDigest: string;
|
|
108
159
|
decision: "approved";
|
|
109
160
|
approvedBy: string;
|
|
110
161
|
approvedAt: string;
|
|
@@ -112,7 +163,7 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
112
163
|
plan: {
|
|
113
164
|
schemaVersion: "treeseed.hosted-topology-plan/v1";
|
|
114
165
|
blockers: {
|
|
115
|
-
code: "connection-unavailable" | "dependency-cycle" | "observation-unhealthy" | "adoption-drift";
|
|
166
|
+
code: "connection-unavailable" | "state-backend-unavailable" | "dependency-cycle" | "observation-unhealthy" | "adoption-drift";
|
|
116
167
|
message: string;
|
|
117
168
|
resourceId?: string | undefined;
|
|
118
169
|
}[];
|
|
@@ -120,12 +171,12 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
120
171
|
actions: {
|
|
121
172
|
action: "noop" | "create" | "adopt" | "update";
|
|
122
173
|
resourceId: string;
|
|
123
|
-
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
174
|
+
kind: "admin-application" | "pages-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
124
175
|
provider: "cloudflare" | "railway";
|
|
125
176
|
providerResourceId: string | null;
|
|
126
177
|
desiredResource: {
|
|
127
178
|
id: string;
|
|
128
|
-
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
179
|
+
kind: "admin-application" | "pages-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
129
180
|
provider: "cloudflare" | "railway";
|
|
130
181
|
adoption: {
|
|
131
182
|
mode: "adopt-or-create";
|
|
@@ -149,13 +200,43 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
149
200
|
desiredDigest: string;
|
|
150
201
|
previousDigest: string | null;
|
|
151
202
|
}[];
|
|
203
|
+
teamId: string;
|
|
152
204
|
environment: "production" | "staging";
|
|
153
205
|
executable: false;
|
|
154
206
|
artifacts: Record<string, {
|
|
155
207
|
digest: string;
|
|
156
208
|
source: string;
|
|
209
|
+
kind: "archive";
|
|
210
|
+
format: "tar+gzip";
|
|
211
|
+
} | {
|
|
212
|
+
digest: string;
|
|
213
|
+
source: string;
|
|
214
|
+
kind: "file";
|
|
215
|
+
mediaType: string;
|
|
216
|
+
} | {
|
|
217
|
+
digest: string;
|
|
218
|
+
kind: "oci-image";
|
|
219
|
+
identity: string;
|
|
157
220
|
}>;
|
|
158
221
|
planId: string;
|
|
222
|
+
deploymentId: string;
|
|
223
|
+
stackId: string;
|
|
224
|
+
stateBackend: {
|
|
225
|
+
key: string;
|
|
226
|
+
type: "s3";
|
|
227
|
+
schemaVersion: "treeseed.hosted-state-backend/v1";
|
|
228
|
+
teamId: string;
|
|
229
|
+
environment: "production" | "staging";
|
|
230
|
+
deploymentId: string;
|
|
231
|
+
stackId: string;
|
|
232
|
+
connectionRef: string;
|
|
233
|
+
bucket: string;
|
|
234
|
+
region: string;
|
|
235
|
+
encryptionKeyRef: string;
|
|
236
|
+
bindingDigest: string;
|
|
237
|
+
endpoint?: string | undefined;
|
|
238
|
+
usePathStyle?: boolean | undefined;
|
|
239
|
+
} | null;
|
|
159
240
|
providerConnections: Partial<Record<"cloudflare" | "railway", {
|
|
160
241
|
connectionRef: string;
|
|
161
242
|
nonSecretConfig: Record<string, string | number | boolean>;
|
|
@@ -175,7 +256,7 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
175
256
|
resources: {
|
|
176
257
|
state: "missing" | "degraded" | "healthy";
|
|
177
258
|
resourceId: string;
|
|
178
|
-
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
259
|
+
kind: "admin-application" | "pages-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
179
260
|
provider: "cloudflare" | "railway";
|
|
180
261
|
providerResourceId: string | null;
|
|
181
262
|
managedBy: "external" | "treeseed" | null;
|
|
@@ -183,17 +264,21 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
183
264
|
observedAt: string;
|
|
184
265
|
}[];
|
|
185
266
|
planDigest: string;
|
|
267
|
+
teamId: string;
|
|
186
268
|
state: "known-good";
|
|
187
269
|
environment: "production" | "staging";
|
|
188
270
|
completedAt: string;
|
|
189
271
|
receiptId: string;
|
|
272
|
+
deploymentId: string;
|
|
273
|
+
stackId: string;
|
|
190
274
|
declarationDigest: string;
|
|
191
275
|
topologyId: string;
|
|
192
276
|
platformCommit: string;
|
|
277
|
+
backendBindingDigest: string;
|
|
193
278
|
previousResources: {
|
|
194
279
|
state: "missing" | "degraded" | "healthy";
|
|
195
280
|
resourceId: string;
|
|
196
|
-
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
281
|
+
kind: "admin-application" | "pages-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
197
282
|
provider: "cloudflare" | "railway";
|
|
198
283
|
providerResourceId: string | null;
|
|
199
284
|
managedBy: "external" | "treeseed" | null;
|
|
@@ -207,6 +292,7 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
207
292
|
}, {}, {
|
|
208
293
|
rollback: {
|
|
209
294
|
schemaVersion: "treeseed.hosted-topology-rollback/v1";
|
|
295
|
+
teamId: string;
|
|
210
296
|
environment: "production" | "staging";
|
|
211
297
|
operations: {
|
|
212
298
|
action: "noop" | "restore" | "delete-created";
|
|
@@ -214,13 +300,20 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
214
300
|
providerResourceId: string;
|
|
215
301
|
targetDigest: string | null;
|
|
216
302
|
}[];
|
|
303
|
+
deploymentId: string;
|
|
304
|
+
stackId: string;
|
|
305
|
+
backendBindingDigest: string;
|
|
217
306
|
rollbackId: string;
|
|
218
307
|
sourceReceiptId: string;
|
|
219
308
|
rollbackDigest: string;
|
|
220
309
|
};
|
|
221
310
|
approval: {
|
|
222
311
|
schemaVersion: "treeseed.hosted-topology-rollback-approval/v1";
|
|
312
|
+
teamId: string;
|
|
223
313
|
environment: "production" | "staging";
|
|
314
|
+
deploymentId: string;
|
|
315
|
+
stackId: string;
|
|
316
|
+
backendBindingDigest: string;
|
|
224
317
|
decision: "approved";
|
|
225
318
|
approvedBy: string;
|
|
226
319
|
approvedAt: string;
|