@treeseed/sdk 0.13.0-rc.27 → 0.13.0-rc.29
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/agent-capacity/validation/agent-definition-schema.d.ts +84 -84
- package/dist/content/validation/agent-operational-content-schemas.d.ts +20 -20
- package/dist/content/validation/auxiliary-content-schemas.d.ts +28 -28
- package/dist/content/validation/content-model-schemas.d.ts +236 -236
- package/dist/content/validation/portable-content-data.d.ts +27 -27
- package/dist/deployment/catalog.js +10 -0
- package/dist/deployment/index.d.ts +1 -0
- package/dist/deployment/index.js +1 -0
- package/dist/deployment/schemas.d.ts +2079 -333
- package/dist/deployment/schemas.js +93 -5
- package/dist/deployment/topology.d.ts +9 -0
- package/dist/deployment/topology.js +35 -0
- package/package.json +1 -1
|
@@ -8,13 +8,57 @@ const localAlias = z.string().regex(
|
|
|
8
8
|
"Host aliases must use the .localhost namespace."
|
|
9
9
|
);
|
|
10
10
|
const binding = z.string().regex(/^(?:\[[0-9a-f:]+\]|[^:]+):[1-9][0-9]{0,4}$/iu);
|
|
11
|
+
const httpsUrl = z.string().url().refine((value) => new URL(value).protocol === "https:", "Remote TreeSeed connections require HTTPS.");
|
|
11
12
|
const deploymentTrackSchema = z.enum(["stable", "development"]);
|
|
13
|
+
const hostRoleSchema = z.union([
|
|
14
|
+
z.enum(["integrated", "capacity-provider", "control-plane", "knowledge", "ai-gpu", "edge"]),
|
|
15
|
+
identifier
|
|
16
|
+
]);
|
|
17
|
+
const connectionHealthGateSchema = z.object({
|
|
18
|
+
protocol: z.enum(["http", "https", "tcp"]),
|
|
19
|
+
path: z.string().startsWith("/").optional(),
|
|
20
|
+
timeoutSeconds: z.number().int().positive().max(600)
|
|
21
|
+
}).strict();
|
|
22
|
+
const remoteTlsSchema = z.object({
|
|
23
|
+
trust: z.enum(["system", "secret", "spki"]),
|
|
24
|
+
serverName: z.string().min(1).optional(),
|
|
25
|
+
caSecretRef: identifier.optional(),
|
|
26
|
+
spkiSha256: digest.optional()
|
|
27
|
+
}).strict().superRefine((tls, context) => {
|
|
28
|
+
if (tls.trust === "secret" && !tls.caSecretRef) context.addIssue({ code: z.ZodIssueCode.custom, path: ["caSecretRef"], message: "Secret TLS trust requires a CA secret reference." });
|
|
29
|
+
if (tls.trust === "spki" && !tls.spkiSha256) context.addIssue({ code: z.ZodIssueCode.custom, path: ["spkiSha256"], message: "SPKI TLS trust requires an exact SHA-256 pin." });
|
|
30
|
+
});
|
|
31
|
+
const hostConnectionBindingSchema = z.union([
|
|
32
|
+
z.object({
|
|
33
|
+
kind: z.literal("local"),
|
|
34
|
+
componentId: identifier,
|
|
35
|
+
serviceId: identifier,
|
|
36
|
+
endpointId: identifier
|
|
37
|
+
}).strict(),
|
|
38
|
+
z.object({
|
|
39
|
+
kind: z.literal("remote"),
|
|
40
|
+
url: httpsUrl,
|
|
41
|
+
audience: z.string().min(1),
|
|
42
|
+
tls: remoteTlsSchema,
|
|
43
|
+
authentication: z.object({ mode: z.enum(["none", "application", "bearer", "mtls"]), secretRef: identifier.optional() }).strict(),
|
|
44
|
+
healthGate: connectionHealthGateSchema
|
|
45
|
+
}).strict()
|
|
46
|
+
]).superRefine((connection, context) => {
|
|
47
|
+
if (connection.kind === "remote" && connection.authentication.mode !== "none" && !connection.authentication.secretRef) context.addIssue({ code: z.ZodIssueCode.custom, path: ["authentication", "secretRef"], message: "Authenticated remote connections require a secret reference." });
|
|
48
|
+
});
|
|
12
49
|
const hostComponentSchema = z.object({
|
|
13
50
|
enabled: z.boolean(),
|
|
14
51
|
track: deploymentTrackSchema,
|
|
15
52
|
profile: identifier.optional(),
|
|
16
53
|
aliases: z.record(z.string().min(1), localAlias).default({}),
|
|
17
|
-
configuration: z.record(z.unknown()).default({})
|
|
54
|
+
configuration: z.record(z.unknown()).default({}),
|
|
55
|
+
resources: z.object({
|
|
56
|
+
cpuCores: z.number().positive().optional(),
|
|
57
|
+
memoryBytes: z.number().int().positive().optional(),
|
|
58
|
+
storageBytes: z.number().int().positive().optional(),
|
|
59
|
+
gpuDevices: z.array(z.string().min(1)).default([])
|
|
60
|
+
}).strict().default({ gpuDevices: [] }),
|
|
61
|
+
connections: z.record(identifier, hostConnectionBindingSchema).default({})
|
|
18
62
|
}).strict();
|
|
19
63
|
const hostConfigurationSchema = z.object({
|
|
20
64
|
schemaVersion: z.literal("treeseed.host/v1"),
|
|
@@ -22,7 +66,7 @@ const hostConfigurationSchema = z.object({
|
|
|
22
66
|
generation: z.number().int().positive(),
|
|
23
67
|
host: z.object({
|
|
24
68
|
id: identifier,
|
|
25
|
-
role:
|
|
69
|
+
role: hostRoleSchema,
|
|
26
70
|
architecture: z.literal("amd64")
|
|
27
71
|
}).strict(),
|
|
28
72
|
runtime: z.object({ management: z.enum(["managed", "external"]) }).strict(),
|
|
@@ -42,6 +86,16 @@ const hostConfigurationSchema = z.object({
|
|
|
42
86
|
network: z.object({
|
|
43
87
|
manager: z.object({ binding, aliases: z.array(localAlias).min(1), sans: z.array(z.string().min(1)), trustedLanCidrs: z.array(z.string().min(1)) }).strict()
|
|
44
88
|
}).strict(),
|
|
89
|
+
fleet: z.object({
|
|
90
|
+
rolloutGroup: identifier,
|
|
91
|
+
receiptReporting: z.object({
|
|
92
|
+
enabled: z.boolean(),
|
|
93
|
+
connection: hostConnectionBindingSchema.optional(),
|
|
94
|
+
intervalSeconds: z.number().int().min(60).max(86400)
|
|
95
|
+
}).strict()
|
|
96
|
+
}).strict().superRefine((fleet, context) => {
|
|
97
|
+
if (fleet.receiptReporting.enabled && fleet.receiptReporting.connection?.kind !== "remote") context.addIssue({ code: z.ZodIssueCode.custom, path: ["receiptReporting", "connection"], message: "Enabled fleet receipt reporting requires an explicit remote connection." });
|
|
98
|
+
}),
|
|
45
99
|
secrets: z.record(identifier, z.object({
|
|
46
100
|
provider: z.enum(["file", "systemd-credential", "vault", "aws-secrets-manager"]),
|
|
47
101
|
reference: z.string().min(1)
|
|
@@ -66,11 +120,20 @@ const packageRuntimeSchema = z.object({
|
|
|
66
120
|
schemaVersion: z.literal("treeseed.package-runtime/v1"),
|
|
67
121
|
componentId: identifier,
|
|
68
122
|
version: packageVersion,
|
|
69
|
-
compose: z.object({
|
|
123
|
+
compose: z.object({
|
|
124
|
+
projectName: identifier,
|
|
125
|
+
files: z.array(z.object({ path: z.string().min(1), digest }).strict()).min(1)
|
|
126
|
+
}).strict(),
|
|
70
127
|
services: z.array(z.object({ id: identifier, composeService: identifier, endpoints: z.array(packageEndpointSchema) }).strict()).min(1),
|
|
71
128
|
stateVolumes: z.array(z.object({ id: identifier, volume: z.string().min(1), backup: z.enum(["required", "optional", "none"]) }).strict()),
|
|
72
129
|
migrations: z.array(z.object({ id: identifier, order: z.number().int().nonnegative(), backupRequired: z.boolean() }).strict()),
|
|
73
|
-
requiredCapabilities: z.array(identifier)
|
|
130
|
+
requiredCapabilities: z.array(identifier),
|
|
131
|
+
dependencies: z.array(z.object({
|
|
132
|
+
id: identifier,
|
|
133
|
+
capability: identifier,
|
|
134
|
+
locality: z.enum(["local", "remote", "either"]),
|
|
135
|
+
optional: z.boolean().default(false)
|
|
136
|
+
}).strict()).default([])
|
|
74
137
|
}).strict().superRefine((runtime, context) => {
|
|
75
138
|
const endpointIds = runtime.services.flatMap((service) => service.endpoints.map((endpoint) => `${service.id}.${endpoint.id}`));
|
|
76
139
|
if (new Set(endpointIds).size !== endpointIds.length) context.addIssue({ code: z.ZodIssueCode.custom, path: ["services"], message: "Endpoint identities must be unique within a component." });
|
|
@@ -81,6 +144,8 @@ const componentReleaseSchema = z.object({
|
|
|
81
144
|
schemaVersion: z.literal("treeseed.component-release/v1"),
|
|
82
145
|
componentId: identifier,
|
|
83
146
|
release: packageVersion,
|
|
147
|
+
applicationVersion: packageVersion,
|
|
148
|
+
revision: z.number().int().positive(),
|
|
84
149
|
track: deploymentTrackSchema,
|
|
85
150
|
source: z.object({ repository: z.string().regex(/^treeseed-ai\/[a-z0-9-]+$/u), commit: gitCommit }).strict(),
|
|
86
151
|
stableBase: z.object({ releaseRange: z.string().min(1), compatibilityId: identifier, catalogDigest: digest.nullable() }).strict().nullable(),
|
|
@@ -95,6 +160,26 @@ const componentReleaseSchema = z.object({
|
|
|
95
160
|
if (release.track === "development" && !release.stableBase) context.addIssue({ code: z.ZodIssueCode.custom, path: ["stableBase"], message: "Development releases require an exact stable-base binding." });
|
|
96
161
|
if (release.track === "stable" && release.stableBase) context.addIssue({ code: z.ZodIssueCode.custom, path: ["stableBase"], message: "Stable releases cannot be overlays." });
|
|
97
162
|
});
|
|
163
|
+
const lockedArtifactSchema = z.object({ url: z.string().url(), sha256: z.string().regex(/^[a-f0-9]{64}$/u) }).strict();
|
|
164
|
+
const integrationReleaseSchema = z.object({
|
|
165
|
+
schemaVersion: z.literal("treeseed.integration-release/v1"),
|
|
166
|
+
release: packageVersion,
|
|
167
|
+
track: deploymentTrackSchema,
|
|
168
|
+
compatibilityId: identifier,
|
|
169
|
+
platform: z.object({ repository: z.literal("treeseed-ai/platform"), commit: gitCommit }).strict(),
|
|
170
|
+
deployment: z.object({ repository: z.literal("treeseed-ai/deployment"), commit: gitCommit, tag: z.string().min(1) }).strict(),
|
|
171
|
+
hostPayloads: z.array(z.object({ id: identifier, packageName: z.string().min(1), version: packageVersion, artifact: lockedArtifactSchema }).strict()),
|
|
172
|
+
components: z.array(z.object({
|
|
173
|
+
componentId: identifier,
|
|
174
|
+
release: packageVersion,
|
|
175
|
+
manifest: lockedArtifactSchema,
|
|
176
|
+
files: z.array(z.object({ path: z.string().min(1), artifact: lockedArtifactSchema }).strict()).min(1)
|
|
177
|
+
}).strict()).min(1),
|
|
178
|
+
createdAt: z.string().datetime()
|
|
179
|
+
}).strict().superRefine((release, context) => {
|
|
180
|
+
const components = release.components.map((component) => component.componentId);
|
|
181
|
+
if (new Set(components).size !== components.length) context.addIssue({ code: z.ZodIssueCode.custom, path: ["components"], message: "Integration releases cannot select a component more than once." });
|
|
182
|
+
});
|
|
98
183
|
const releaseCatalogSchema = z.object({
|
|
99
184
|
schemaVersion: z.literal("treeseed.release-catalog/v1"),
|
|
100
185
|
release: packageVersion,
|
|
@@ -132,7 +217,7 @@ const hostUpdateSchema = z.object({
|
|
|
132
217
|
activation: z.object({ policy: z.enum(["maintenance-window", "continuous"]), eligibleAt: z.string().datetime(), jitterSeconds: z.number().int().nonnegative() }).strict(),
|
|
133
218
|
state: z.enum(["planned", "draining", "applying", "healthy", "failed", "rolled-back"])
|
|
134
219
|
}).strict();
|
|
135
|
-
const hostReceiptSchema = z.object({ schemaVersion: z.literal("treeseed.host-receipt/v1"), receiptId: identifier, planId: identifier, state: z.enum(["known-good", "degraded", "rolled-back"]), configurationDigest: digest, catalogDigest: digest, packages: z.array(catalogPackageSchema), images: z.array(catalogImageSchema), completedAt: z.string().datetime() }).strict();
|
|
220
|
+
const hostReceiptSchema = z.object({ schemaVersion: z.literal("treeseed.host-receipt/v1"), receiptId: identifier, planId: identifier, state: z.enum(["known-good", "degraded", "rolled-back"]), hostId: identifier, role: hostRoleSchema, rolloutGroup: identifier, configurationDigest: digest, catalogDigest: digest, packages: z.array(catalogPackageSchema), images: z.array(catalogImageSchema), runtimes: z.array(z.object({ componentId: identifier, release: packageVersion, runtimeDigest: digest }).strict()), completedAt: z.string().datetime() }).strict();
|
|
136
221
|
const hostBackupSchema = z.object({
|
|
137
222
|
schemaVersion: z.literal("treeseed.host-backup/v1"),
|
|
138
223
|
backupId: identifier,
|
|
@@ -174,11 +259,14 @@ export {
|
|
|
174
259
|
hostBootstrapSchema,
|
|
175
260
|
hostComponentSchema,
|
|
176
261
|
hostConfigurationSchema,
|
|
262
|
+
hostConnectionBindingSchema,
|
|
177
263
|
hostMigrationSchema,
|
|
178
264
|
hostPlanSchema,
|
|
179
265
|
hostReceiptSchema,
|
|
180
266
|
hostRecoverySchema,
|
|
267
|
+
hostRoleSchema,
|
|
181
268
|
hostUpdateSchema,
|
|
269
|
+
integrationReleaseSchema,
|
|
182
270
|
packageEndpointSchema,
|
|
183
271
|
packageRuntimeSchema,
|
|
184
272
|
releaseCatalogSchema
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ComponentRelease, HostConfiguration } from './schemas.js';
|
|
2
|
+
export interface TopologyBlocker {
|
|
3
|
+
code: 'missing-connection' | 'invalid-locality' | 'missing-local-component' | 'missing-local-endpoint';
|
|
4
|
+
componentId: string;
|
|
5
|
+
dependencyId: string;
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function collectTopologyBlockers(host: HostConfiguration, releases: ComponentRelease[]): TopologyBlocker[];
|
|
9
|
+
export declare function hostNeedsEdge(host: HostConfiguration, releases: ComponentRelease[]): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
function collectTopologyBlockers(host, releases) {
|
|
2
|
+
const selected = new Map(releases.map((release) => [release.componentId, release]));
|
|
3
|
+
const blockers = [];
|
|
4
|
+
for (const release of releases) {
|
|
5
|
+
const selection = host.components[release.componentId];
|
|
6
|
+
if (!selection?.enabled) continue;
|
|
7
|
+
for (const dependency of release.runtime.dependencies) {
|
|
8
|
+
const connection = selection.connections[dependency.id];
|
|
9
|
+
if (!connection) {
|
|
10
|
+
if (!dependency.optional) blockers.push({ code: "missing-connection", componentId: release.componentId, dependencyId: dependency.id, message: `${release.componentId} requires an explicit ${dependency.id} connection.` });
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (dependency.locality !== "either" && connection.kind !== dependency.locality) {
|
|
14
|
+
blockers.push({ code: "invalid-locality", componentId: release.componentId, dependencyId: dependency.id, message: `${release.componentId}.${dependency.id} requires a ${dependency.locality} connection.` });
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (connection.kind !== "local") continue;
|
|
18
|
+
const target = selected.get(connection.componentId);
|
|
19
|
+
if (!target || !host.components[connection.componentId]?.enabled) {
|
|
20
|
+
blockers.push({ code: "missing-local-component", componentId: release.componentId, dependencyId: dependency.id, message: `${release.componentId}.${dependency.id} references disabled or unselected component ${connection.componentId}.` });
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const service = target.runtime.services.find((candidate) => candidate.id === connection.serviceId);
|
|
24
|
+
if (!service?.endpoints.some((endpoint) => endpoint.id === connection.endpointId)) blockers.push({ code: "missing-local-endpoint", componentId: release.componentId, dependencyId: dependency.id, message: `${release.componentId}.${dependency.id} references unknown endpoint ${connection.componentId}.${connection.serviceId}.${connection.endpointId}.` });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return blockers.sort((left, right) => `${left.componentId}.${left.dependencyId}`.localeCompare(`${right.componentId}.${right.dependencyId}`));
|
|
28
|
+
}
|
|
29
|
+
function hostNeedsEdge(host, releases) {
|
|
30
|
+
return releases.some((release) => host.components[release.componentId]?.enabled && release.runtime.services.some((service) => service.endpoints.some((endpoint) => endpoint.visibility === "host")));
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
collectTopologyBlockers,
|
|
34
|
+
hostNeedsEdge
|
|
35
|
+
};
|