@treeseed/sdk 0.13.0-rc.71 → 0.13.0-rc.73
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/deployment/hosted-topology.d.ts +666 -26
- package/dist/deployment/hosted-topology.js +63 -14
- package/dist/operator-contracts/canonical-command-tree.js +10 -0
- package/dist/operator-contracts/catalog/hosted-topology-operations.d.ts +214 -0
- package/dist/operator-contracts/catalog/hosted-topology-operations.js +77 -0
- package/dist/operator-contracts/control-plane-operations.d.ts +216 -0
- 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
|
@@ -27,6 +27,14 @@ const resourceProviderKinds = {
|
|
|
27
27
|
};
|
|
28
28
|
const sensitiveKey = /(?:credential|password|private.?key|registration.?code|secret|token)/iu;
|
|
29
29
|
const personalPath = /(?:^|[\s'"`:=])(?:\/home\/[^/\s]+|\/Users\/[^/\s]+|[A-Za-z]:\\Users\\[^\\\s]+)/u;
|
|
30
|
+
const hostedResourceDeclarationSchema = z.object({
|
|
31
|
+
id: identifier,
|
|
32
|
+
provider: hostedProviderSchema,
|
|
33
|
+
kind: hostedResourceKindSchema,
|
|
34
|
+
dependsOn: z.array(identifier).default([]),
|
|
35
|
+
parameters: z.record(identifier, parameterSchema).default({}),
|
|
36
|
+
adoption: z.object({ mode: z.literal("adopt-or-create"), externalIdInput: identifier.optional(), replacement: z.literal("forbidden") }).strict()
|
|
37
|
+
}).strict();
|
|
30
38
|
const hostedTopologyDeclarationSchema = z.object({
|
|
31
39
|
schemaVersion: z.literal("treeseed.hosted-topology/v1"),
|
|
32
40
|
id: identifier,
|
|
@@ -35,14 +43,7 @@ const hostedTopologyDeclarationSchema = z.object({
|
|
|
35
43
|
platform: z.object({ repository: z.literal("treeseed-ai/platform"), commit: gitCommit }).strict(),
|
|
36
44
|
providerConnections: z.record(hostedProviderSchema, z.object({ connectionRef: identifier }).strict()),
|
|
37
45
|
artifacts: z.record(identifier, z.object({ digest, source: z.string().url() }).strict()),
|
|
38
|
-
resources: z.array(
|
|
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
|
+
resources: z.array(hostedResourceDeclarationSchema).min(1)
|
|
46
47
|
}).strict().superRefine((declaration, context) => {
|
|
47
48
|
const ids = declaration.resources.map(({ id }) => id);
|
|
48
49
|
if (new Set(ids).size !== ids.length) context.addIssue({ code: z.ZodIssueCode.custom, path: ["resources"], message: "Hosted topology resource identities must be unique." });
|
|
@@ -63,7 +64,7 @@ const hostedResourceObservationSchema = z.object({
|
|
|
63
64
|
observedDigest: digest.nullable(),
|
|
64
65
|
observedAt: timestamp
|
|
65
66
|
}).strict();
|
|
66
|
-
const
|
|
67
|
+
const hostedTopologyPlanShape = {
|
|
67
68
|
schemaVersion: z.literal("treeseed.hosted-topology-plan/v1"),
|
|
68
69
|
planId: z.string().regex(/^topology-plan-[a-f0-9]{16}$/u),
|
|
69
70
|
planDigest: digest,
|
|
@@ -76,6 +77,7 @@ const hostedTopologyPlanSchema = z.object({
|
|
|
76
77
|
provider: hostedProviderSchema,
|
|
77
78
|
kind: hostedResourceKindSchema,
|
|
78
79
|
action: z.enum(["create", "adopt", "update", "noop"]),
|
|
80
|
+
desiredResource: hostedResourceDeclarationSchema,
|
|
79
81
|
desiredDigest: digest,
|
|
80
82
|
previousDigest: digest.nullable(),
|
|
81
83
|
providerResourceId: z.string().min(1).max(512).nullable()
|
|
@@ -83,7 +85,28 @@ const hostedTopologyPlanSchema = z.object({
|
|
|
83
85
|
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
86
|
approvalRequired: z.boolean(),
|
|
85
87
|
executable: z.literal(false)
|
|
86
|
-
}
|
|
88
|
+
};
|
|
89
|
+
function verifyPlanBinding(plan, context) {
|
|
90
|
+
for (const [index, action] of plan.actions.entries()) {
|
|
91
|
+
const desired = hostedResourceDeclarationSchema.parse(action.desiredResource);
|
|
92
|
+
if (desired.id !== action.resourceId || desired.provider !== action.provider || desired.kind !== action.kind)
|
|
93
|
+
context.addIssue({ code: z.ZodIssueCode.custom, path: ["actions", index, "desiredResource"], message: "Hosted plan action identity must match its desired resource specification." });
|
|
94
|
+
if (deploymentDigest(desired) !== action.desiredDigest)
|
|
95
|
+
context.addIssue({ code: z.ZodIssueCode.custom, path: ["actions", index, "desiredDigest"], message: "Hosted plan desired digest must bind its complete desired resource specification." });
|
|
96
|
+
}
|
|
97
|
+
const core = {
|
|
98
|
+
declarationDigest: plan.declarationDigest,
|
|
99
|
+
topologyId: plan.topologyId,
|
|
100
|
+
environment: plan.environment,
|
|
101
|
+
platformCommit: plan.platformCommit,
|
|
102
|
+
actions: plan.actions,
|
|
103
|
+
blockers: plan.blockers
|
|
104
|
+
};
|
|
105
|
+
const expected = deploymentDigest(core);
|
|
106
|
+
if (expected !== plan.planDigest || plan.planId !== `topology-plan-${expected.slice(7, 23)}`)
|
|
107
|
+
context.addIssue({ code: z.ZodIssueCode.custom, path: ["planDigest"], message: "Hosted topology plan identity must bind the exact canonical plan." });
|
|
108
|
+
}
|
|
109
|
+
const hostedTopologyPlanSchema = z.object(hostedTopologyPlanShape).strict().superRefine(verifyPlanBinding);
|
|
87
110
|
const hostedTopologyApprovalSchema = z.object({
|
|
88
111
|
schemaVersion: z.literal("treeseed.hosted-topology-approval/v1"),
|
|
89
112
|
planDigest: digest,
|
|
@@ -92,10 +115,11 @@ const hostedTopologyApprovalSchema = z.object({
|
|
|
92
115
|
approvedBy: z.string().min(1).max(256),
|
|
93
116
|
approvedAt: timestamp
|
|
94
117
|
}).strict();
|
|
95
|
-
const authorizedHostedTopologyPlanSchema =
|
|
118
|
+
const authorizedHostedTopologyPlanSchema = z.object({
|
|
119
|
+
...hostedTopologyPlanShape,
|
|
96
120
|
executable: z.literal(true),
|
|
97
121
|
approval: hostedTopologyApprovalSchema.nullable()
|
|
98
|
-
}).omit({ approvalRequired: true });
|
|
122
|
+
}).strict().omit({ approvalRequired: true }).superRefine(verifyPlanBinding);
|
|
99
123
|
const hostedTopologyReceiptSchema = z.object({
|
|
100
124
|
schemaVersion: z.literal("treeseed.hosted-topology-receipt/v1"),
|
|
101
125
|
receiptId: z.string().regex(/^topology-receipt-[a-f0-9]{16}$/u),
|
|
@@ -113,6 +137,7 @@ const hostedTopologyRollbackSchema = z.object({
|
|
|
113
137
|
schemaVersion: z.literal("treeseed.hosted-topology-rollback/v1"),
|
|
114
138
|
rollbackId: z.string().regex(/^topology-rollback-[a-f0-9]{16}$/u),
|
|
115
139
|
sourceReceiptId: z.string().regex(/^topology-receipt-[a-f0-9]{16}$/u),
|
|
140
|
+
environment: z.enum(["staging", "production"]),
|
|
116
141
|
operations: z.array(z.object({
|
|
117
142
|
resourceId: identifier,
|
|
118
143
|
action: z.enum(["restore", "delete-created", "noop"]),
|
|
@@ -121,6 +146,14 @@ const hostedTopologyRollbackSchema = z.object({
|
|
|
121
146
|
}).strict()),
|
|
122
147
|
rollbackDigest: digest
|
|
123
148
|
}).strict();
|
|
149
|
+
const hostedTopologyRollbackApprovalSchema = z.object({
|
|
150
|
+
schemaVersion: z.literal("treeseed.hosted-topology-rollback-approval/v1"),
|
|
151
|
+
rollbackDigest: digest,
|
|
152
|
+
environment: z.enum(["staging", "production"]),
|
|
153
|
+
decision: z.literal("approved"),
|
|
154
|
+
approvedBy: z.string().min(1).max(256),
|
|
155
|
+
approvedAt: timestamp
|
|
156
|
+
}).strict();
|
|
124
157
|
function observationMap(items, declaration) {
|
|
125
158
|
const resources = new Map(declaration.resources.map((resource) => [resource.id, resource]));
|
|
126
159
|
const observations = /* @__PURE__ */ new Map();
|
|
@@ -173,7 +206,16 @@ function planHostedTopology(input) {
|
|
|
173
206
|
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.` });
|
|
174
207
|
else action = "update";
|
|
175
208
|
}
|
|
176
|
-
return {
|
|
209
|
+
return {
|
|
210
|
+
resourceId: resource.id,
|
|
211
|
+
provider: resource.provider,
|
|
212
|
+
kind: resource.kind,
|
|
213
|
+
action,
|
|
214
|
+
desiredResource: resource,
|
|
215
|
+
desiredDigest,
|
|
216
|
+
previousDigest: observation?.observedDigest ?? null,
|
|
217
|
+
providerResourceId: observation?.providerResourceId ?? null
|
|
218
|
+
};
|
|
177
219
|
});
|
|
178
220
|
const declarationDigest = deploymentDigest(normalizedDeclaration);
|
|
179
221
|
const core = { declarationDigest, topologyId: declaration.id, environment: declaration.environment, platformCommit: declaration.platform.commit, actions, blockers };
|
|
@@ -211,10 +253,16 @@ function planHostedTopologyRollback(receiptInput) {
|
|
|
211
253
|
return { resourceId: resource.resourceId, action: !prior || prior.state === "missing" ? "delete-created" : prior.observedDigest === resource.observedDigest ? "noop" : "restore", providerResourceId: resource.providerResourceId, targetDigest: prior?.observedDigest ?? null };
|
|
212
254
|
}).sort((left, right) => left.resourceId.localeCompare(right.resourceId));
|
|
213
255
|
const rollbackDigest = deploymentDigest({ sourceReceiptId: receipt.receiptId, operations });
|
|
214
|
-
return hostedTopologyRollbackSchema.parse({ schemaVersion: "treeseed.hosted-topology-rollback/v1", rollbackId: `topology-rollback-${rollbackDigest.slice(7, 23)}`, sourceReceiptId: receipt.receiptId, operations, rollbackDigest });
|
|
256
|
+
return hostedTopologyRollbackSchema.parse({ schemaVersion: "treeseed.hosted-topology-rollback/v1", rollbackId: `topology-rollback-${rollbackDigest.slice(7, 23)}`, sourceReceiptId: receipt.receiptId, environment: receipt.environment, operations, rollbackDigest });
|
|
257
|
+
}
|
|
258
|
+
function authorizeHostedTopologyRollback(rollbackInput, approvalInput) {
|
|
259
|
+
const rollback = hostedTopologyRollbackSchema.parse(rollbackInput), approval = hostedTopologyRollbackApprovalSchema.parse(approvalInput);
|
|
260
|
+
if (approval.rollbackDigest !== rollback.rollbackDigest || approval.environment !== rollback.environment) throw new Error("Hosted topology rollback approval does not bind the exact rollback and environment.");
|
|
261
|
+
return { rollback, approval };
|
|
215
262
|
}
|
|
216
263
|
export {
|
|
217
264
|
authorizeHostedTopologyPlan,
|
|
265
|
+
authorizeHostedTopologyRollback,
|
|
218
266
|
authorizedHostedTopologyPlanSchema,
|
|
219
267
|
hostedProviderSchema,
|
|
220
268
|
hostedResourceKindSchema,
|
|
@@ -223,6 +271,7 @@ export {
|
|
|
223
271
|
hostedTopologyDeclarationSchema,
|
|
224
272
|
hostedTopologyPlanSchema,
|
|
225
273
|
hostedTopologyReceiptSchema,
|
|
274
|
+
hostedTopologyRollbackApprovalSchema,
|
|
226
275
|
hostedTopologyRollbackSchema,
|
|
227
276
|
planHostedTopology,
|
|
228
277
|
planHostedTopologyRollback,
|
|
@@ -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", [
|
|
@@ -0,0 +1,214 @@
|
|
|
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
|
+
desiredResource: {
|
|
60
|
+
id: string;
|
|
61
|
+
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
62
|
+
provider: "cloudflare" | "railway";
|
|
63
|
+
adoption: {
|
|
64
|
+
mode: "adopt-or-create";
|
|
65
|
+
replacement: "forbidden";
|
|
66
|
+
externalIdInput?: string | undefined;
|
|
67
|
+
};
|
|
68
|
+
dependsOn?: string[] | undefined;
|
|
69
|
+
parameters?: Record<string, {
|
|
70
|
+
input: string;
|
|
71
|
+
} | {
|
|
72
|
+
artifact: string;
|
|
73
|
+
} | {
|
|
74
|
+
resourceOutput: {
|
|
75
|
+
resourceId: string;
|
|
76
|
+
output: string;
|
|
77
|
+
};
|
|
78
|
+
} | {
|
|
79
|
+
literal: string | number | boolean;
|
|
80
|
+
}> | undefined;
|
|
81
|
+
};
|
|
82
|
+
desiredDigest: string;
|
|
83
|
+
previousDigest: string | null;
|
|
84
|
+
}[];
|
|
85
|
+
environment: "production" | "staging";
|
|
86
|
+
executable: false;
|
|
87
|
+
planId: string;
|
|
88
|
+
declarationDigest: string;
|
|
89
|
+
topologyId: string;
|
|
90
|
+
platformCommit: string;
|
|
91
|
+
approvalRequired: boolean;
|
|
92
|
+
}>;
|
|
93
|
+
readonly apply: import("../control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
94
|
+
teamId: string;
|
|
95
|
+
}, {}, {
|
|
96
|
+
approval: {
|
|
97
|
+
schemaVersion: "treeseed.hosted-topology-approval/v1";
|
|
98
|
+
planDigest: string;
|
|
99
|
+
environment: "production" | "staging";
|
|
100
|
+
decision: "approved";
|
|
101
|
+
approvedBy: string;
|
|
102
|
+
approvedAt: string;
|
|
103
|
+
};
|
|
104
|
+
plan: {
|
|
105
|
+
schemaVersion: "treeseed.hosted-topology-plan/v1";
|
|
106
|
+
blockers: {
|
|
107
|
+
code: "connection-unavailable" | "dependency-cycle" | "observation-unhealthy" | "adoption-drift";
|
|
108
|
+
message: string;
|
|
109
|
+
resourceId?: string | undefined;
|
|
110
|
+
}[];
|
|
111
|
+
planDigest: string;
|
|
112
|
+
actions: {
|
|
113
|
+
action: "noop" | "create" | "adopt" | "update";
|
|
114
|
+
resourceId: string;
|
|
115
|
+
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
116
|
+
provider: "cloudflare" | "railway";
|
|
117
|
+
providerResourceId: string | null;
|
|
118
|
+
desiredResource: {
|
|
119
|
+
id: string;
|
|
120
|
+
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
121
|
+
provider: "cloudflare" | "railway";
|
|
122
|
+
adoption: {
|
|
123
|
+
mode: "adopt-or-create";
|
|
124
|
+
replacement: "forbidden";
|
|
125
|
+
externalIdInput?: string | undefined;
|
|
126
|
+
};
|
|
127
|
+
dependsOn?: string[] | undefined;
|
|
128
|
+
parameters?: Record<string, {
|
|
129
|
+
input: string;
|
|
130
|
+
} | {
|
|
131
|
+
artifact: string;
|
|
132
|
+
} | {
|
|
133
|
+
resourceOutput: {
|
|
134
|
+
resourceId: string;
|
|
135
|
+
output: string;
|
|
136
|
+
};
|
|
137
|
+
} | {
|
|
138
|
+
literal: string | number | boolean;
|
|
139
|
+
}> | undefined;
|
|
140
|
+
};
|
|
141
|
+
desiredDigest: string;
|
|
142
|
+
previousDigest: string | null;
|
|
143
|
+
}[];
|
|
144
|
+
environment: "production" | "staging";
|
|
145
|
+
executable: false;
|
|
146
|
+
planId: string;
|
|
147
|
+
declarationDigest: string;
|
|
148
|
+
topologyId: string;
|
|
149
|
+
platformCommit: string;
|
|
150
|
+
approvalRequired: boolean;
|
|
151
|
+
};
|
|
152
|
+
}, Record<string, unknown>>;
|
|
153
|
+
readonly status: import("../control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
154
|
+
teamId: string;
|
|
155
|
+
}, {}, undefined, {
|
|
156
|
+
operation: Record<string, unknown> | null;
|
|
157
|
+
receipt: {
|
|
158
|
+
schemaVersion: "treeseed.hosted-topology-receipt/v1";
|
|
159
|
+
resources: {
|
|
160
|
+
state: "missing" | "degraded" | "healthy";
|
|
161
|
+
resourceId: string;
|
|
162
|
+
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
163
|
+
provider: "cloudflare" | "railway";
|
|
164
|
+
providerResourceId: string | null;
|
|
165
|
+
managedBy: "external" | "treeseed" | null;
|
|
166
|
+
observedDigest: string | null;
|
|
167
|
+
observedAt: string;
|
|
168
|
+
}[];
|
|
169
|
+
planDigest: string;
|
|
170
|
+
state: "known-good";
|
|
171
|
+
environment: "production" | "staging";
|
|
172
|
+
completedAt: string;
|
|
173
|
+
receiptId: string;
|
|
174
|
+
declarationDigest: string;
|
|
175
|
+
topologyId: string;
|
|
176
|
+
platformCommit: string;
|
|
177
|
+
previousResources: {
|
|
178
|
+
state: "missing" | "degraded" | "healthy";
|
|
179
|
+
resourceId: string;
|
|
180
|
+
kind: "admin-application" | "dns-record" | "tls-policy" | "api-proxy" | "control-plane-api" | "postgresql" | "operations-runner" | "treedx-service";
|
|
181
|
+
provider: "cloudflare" | "railway";
|
|
182
|
+
providerResourceId: string | null;
|
|
183
|
+
managedBy: "external" | "treeseed" | null;
|
|
184
|
+
observedDigest: string | null;
|
|
185
|
+
observedAt: string;
|
|
186
|
+
}[];
|
|
187
|
+
} | null;
|
|
188
|
+
}>;
|
|
189
|
+
readonly rollback: import("../control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
190
|
+
teamId: string;
|
|
191
|
+
}, {}, {
|
|
192
|
+
rollback: {
|
|
193
|
+
schemaVersion: "treeseed.hosted-topology-rollback/v1";
|
|
194
|
+
environment: "production" | "staging";
|
|
195
|
+
operations: {
|
|
196
|
+
action: "noop" | "restore" | "delete-created";
|
|
197
|
+
resourceId: string;
|
|
198
|
+
providerResourceId: string;
|
|
199
|
+
targetDigest: string | null;
|
|
200
|
+
}[];
|
|
201
|
+
rollbackId: string;
|
|
202
|
+
sourceReceiptId: string;
|
|
203
|
+
rollbackDigest: string;
|
|
204
|
+
};
|
|
205
|
+
approval: {
|
|
206
|
+
schemaVersion: "treeseed.hosted-topology-rollback-approval/v1";
|
|
207
|
+
environment: "production" | "staging";
|
|
208
|
+
decision: "approved";
|
|
209
|
+
approvedBy: string;
|
|
210
|
+
approvedAt: string;
|
|
211
|
+
rollbackDigest: string;
|
|
212
|
+
};
|
|
213
|
+
}, Record<string, unknown>>;
|
|
214
|
+
};
|
|
@@ -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
|
+
};
|