-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathenvironment-artifact.zod.ts
More file actions
313 lines (269 loc) · 12.2 KB
/
Copy pathenvironment-artifact.zod.ts
File metadata and controls
313 lines (269 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
import { z } from 'zod';
/**
* # Environment Artifact Format Protocol (v0)
*
* Defines the immutable envelope produced by `objectstack compile` and consumed
* by the ObjectStack runtime at boot. The artifact carries everything a runtime instance
* needs to hydrate an environment kernel without reading control-plane DB rows
* directly.
*
* ## Boundary
*
* - **Artifact (this schema):** environment metadata + inlined function code +
* plugin/driver requirements. Immutable, content-addressable via `commitId`
* and `checksum`.
* - **Deployment Config (NOT in this schema):** business DB coordinates,
* credentials, environment identity, secrets. Injected at runtime.
*
* See {@link content/docs/concepts/north-star.mdx} §6.3 for the runtime-inputs
* boundary, and {@link ROADMAP.md} M1 for the milestone definition.
*
* ## Storage / Distribution
*
* v0 stores the full payload inline. Future revisions may swap `metadata` /
* `functions` for a `payloadRef` that points at out-of-band storage (S3,
* signed URL). The envelope shape preserves room for that indirection without
* a breaking schema bump.
*/
// ==========================================
// Constants
// ==========================================
/** Current artifact schema version. Bump on every breaking envelope change. */
export const ENVIRONMENT_ARTIFACT_SCHEMA_VERSION = '0.1' as const;
/** Hash algorithms permitted for artifact checksums. */
export const EnvironmentArtifactHashAlgorithmEnum = z
.enum(['sha256', 'sha384', 'sha512'])
.describe('Hash algorithm used for the artifact checksum');
export type EnvironmentArtifactHashAlgorithm = z.infer<typeof EnvironmentArtifactHashAlgorithmEnum>;
// ==========================================
// Checksum
// ==========================================
/**
* Content-addressable checksum of the canonical JSON-serialized artifact body
* (everything except the `checksum` field itself). Used by the runtime to verify
* that the artifact bytes were not tampered with in transit and to key the
* local artifact cache.
*/
export const EnvironmentArtifactChecksumSchema = z
.object({
algorithm: EnvironmentArtifactHashAlgorithmEnum.default('sha256'),
value: z
.string()
.regex(/^[a-f0-9]+$/, 'Checksum value must be lowercase hexadecimal')
.describe('Hex-encoded digest of the artifact body'),
})
.describe('Artifact integrity checksum');
export type EnvironmentArtifactChecksum = z.infer<typeof EnvironmentArtifactChecksumSchema>;
// ==========================================
// Function code packaging
// ==========================================
/**
* Languages supported for inlined function code. The runtime decides how to
* load each language; v0 only commits to JavaScript bytes shipping unmodified.
*/
export const EnvironmentArtifactFunctionLanguageEnum = z
.enum(['javascript', 'typescript'])
.describe('Source language of the function code');
export type EnvironmentArtifactFunctionLanguage = z.infer<typeof EnvironmentArtifactFunctionLanguageEnum>;
/**
* A single function (object trigger, computed field, action, etc.) packaged
* into the artifact. Function code is inlined as a UTF-8 string; binary or
* out-of-band storage is reserved for a future revision via `payloadRef`.
*/
export const EnvironmentArtifactFunctionSchema = z
.object({
/** Globally unique function name (snake_case). */
name: z
.string()
.regex(/^[a-z_][a-z0-9_]*$/)
.describe('Function machine name (snake_case)'),
/** Source language of the inlined `code` field. */
language: EnvironmentArtifactFunctionLanguageEnum.default('javascript'),
/** UTF-8 encoded function source. Must be self-contained. */
code: z.string().describe('Inlined function source'),
/**
* Optional provenance pointer: where the code came from in the original
* TypeScript workspace. Useful for debug overlays in Studio.
*/
source: z
.object({
path: z.string().optional().describe('Source file path (relative to project root)'),
exportName: z.string().optional().describe('Exported symbol name'),
})
.optional()
.describe('Source-map metadata for the function'),
/** Hex SHA-256 of `code` for cache invalidation. */
hash: z
.string()
.regex(/^[a-f0-9]+$/)
.optional()
.describe('Hex SHA-256 of the inlined code'),
})
.describe('A single inlined function');
export type EnvironmentArtifactFunction = z.infer<typeof EnvironmentArtifactFunctionSchema>;
// ==========================================
// Plugin / Driver requirements
// ==========================================
/**
* Plugin/driver requirement entry. The runtime uses these to verify that the
* runtime has every plugin the environment depends on before hydrating the kernel.
* Configuration values live in **Deployment Config**, not in the artifact.
*/
export const EnvironmentArtifactRequirementSchema = z
.object({
/** Package id (reverse-domain or short id). */
id: z.string().describe('Plugin/driver package id'),
/** SemVer range required by the environment. */
version: z.string().optional().describe('SemVer range required by the environment'),
})
.describe('A plugin or driver dependency declaration');
export type EnvironmentArtifactRequirement = z.infer<typeof EnvironmentArtifactRequirementSchema>;
/**
* Environment-level manifest captured inside the artifact. Mirrors the parts of
* the package manifest the runtime needs to bootstrap; user-facing manifest
* fields (description, icon, marketplace metadata) are excluded.
*/
export const EnvironmentArtifactManifestSchema = z
.object({
/** Plugins required to run this environment's metadata. */
plugins: z.array(EnvironmentArtifactRequirementSchema).optional(),
/** Drivers required to run this environment's metadata. */
drivers: z.array(EnvironmentArtifactRequirementSchema).optional(),
/** Minimum platform version (mirrors `Manifest.engine`). */
engine: z
.object({
objectstack: z
.string()
.regex(/^[><=~^]*\d+\.\d+\.\d+/)
.describe('ObjectStack platform version requirement (SemVer range)'),
})
.optional(),
})
.describe('Plugin/driver requirements baked into the artifact');
export type EnvironmentArtifactManifest = z.infer<typeof EnvironmentArtifactManifestSchema>;
// ==========================================
// Metadata payload
// ==========================================
/**
* Compiled project metadata. v0 is intentionally permissive: the inner shape
* is validated by the protocol-level `ObjectStackDefinitionSchema` (and per-
* domain Zod schemas) rather than re-validated here, to avoid coupling the
* artifact envelope to every domain schema bump.
*
* Treat this as a typed bag of arrays keyed by metadata category. Unknown
* categories are passed through (`passthrough()`) so older runtime builds can
* boot newer artifacts safely if no breaking changes were made.
*/
export const EnvironmentArtifactMetadataSchema = z
.object({
objects: z.array(z.unknown()).optional(),
fields: z.array(z.unknown()).optional(),
views: z.array(z.unknown()).optional(),
apps: z.array(z.unknown()).optional(),
pages: z.array(z.unknown()).optional(),
dashboards: z.array(z.unknown()).optional(),
reports: z.array(z.unknown()).optional(),
flows: z.array(z.unknown()).optional(),
workflows: z.array(z.unknown()).optional(),
triggers: z.array(z.unknown()).optional(),
agents: z.array(z.unknown()).optional(),
tools: z.array(z.unknown()).optional(),
skills: z.array(z.unknown()).optional(),
permissions: z.array(z.unknown()).optional(),
permissionSets: z.array(z.unknown()).optional(),
// ADR-0090 D3/D2: `roles`/`profiles` categories retired with the concepts
// (stacks declare `positions`; the profile concept is gone). Old artifacts
// carrying them still parse via `passthrough()` — the keys are simply no
// longer part of the declared envelope.
positions: z.array(z.unknown()).optional(),
translations: z.array(z.unknown()).optional(),
datasources: z.array(z.unknown()).optional(),
datasets: z.array(z.unknown()).optional(),
actions: z.array(z.unknown()).optional(),
apis: z.array(z.unknown()).optional(),
})
.passthrough()
.describe('Compiled environment metadata grouped by category');
export type EnvironmentArtifactMetadata = z.infer<typeof EnvironmentArtifactMetadataSchema>;
// ==========================================
// Out-of-band payload reference (reserved)
// ==========================================
/**
* Reserved indirection for moving large payloads out of the inline JSON. v0
* artifacts inline `metadata` and `functions` directly; future revisions can
* set `payloadRef` to a signed URL and omit (or truncate) the inline copies.
*
* Defined now so the envelope shape is stable across the inline-only ↔ S3
* transition.
*/
export const EnvironmentArtifactPayloadRefSchema = z
.object({
url: z.string().url().describe('Signed URL pointing at the artifact payload'),
expiresAt: z.string().datetime().optional().describe('ISO-8601 expiry timestamp'),
checksum: EnvironmentArtifactChecksumSchema.describe('Checksum of the referenced payload'),
})
.describe('Out-of-band payload reference (reserved for future use)');
export type EnvironmentArtifactPayloadRef = z.infer<typeof EnvironmentArtifactPayloadRefSchema>;
// ==========================================
// Envelope
// ==========================================
/**
* Environment Artifact envelope.
*
* Produced by `objectstack compile`, served by the Environment Artifact API
* (`GET /api/v1/cloud/environments/:environmentId/artifact`), and consumed by the
* runtime metadata loader to hydrate an environment kernel.
*
* Required fields (v0):
* - `schemaVersion`: tracks the envelope itself.
* - `environmentId`: which environment this artifact belongs to.
* - `commitId`: monotonic, content-addressable identifier; cache key.
* - `checksum`: integrity check over the artifact body.
* - `metadata`: compiled metadata grouped by category.
* - `functions`: inlined function code.
* - `manifest`: plugin/driver requirements.
*
* Optional fields (v0):
* - `builtAt`, `builtWith`: provenance.
* - `payloadRef`: reserved for future S3 indirection.
*/
export const EnvironmentArtifactSchema = z
.object({
/** Envelope schema version. Currently always `'0.1'`. */
schemaVersion: z
.literal(ENVIRONMENT_ARTIFACT_SCHEMA_VERSION)
.describe('Environment artifact envelope schema version'),
/** Stable environment identifier from the control plane. */
environmentId: z.string().min(1).describe('Environment identifier (control-plane scoped)'),
/**
* Monotonic, content-addressable revision id assigned by the control plane
* when the artifact is published. Used as a cache key by the runtime and as
* the rollback target by Studio.
*/
commitId: z.string().min(1).describe('Content-addressable revision id'),
/** Integrity checksum over the canonical artifact body. */
checksum: EnvironmentArtifactChecksumSchema,
/** ISO-8601 build timestamp. */
builtAt: z
.string()
.datetime()
.optional()
.describe('ISO-8601 timestamp of when the artifact was built'),
/** Build tool identifier (e.g. `"objectstack-cli@3.4.0"`). */
builtWith: z.string().optional().describe('Build tool identifier'),
/** Compiled environment metadata grouped by category. */
metadata: EnvironmentArtifactMetadataSchema,
/** Inlined function code. Empty array if the environment has no functions. */
functions: z
.array(EnvironmentArtifactFunctionSchema)
.default([])
.describe('Inlined function code packaged with the artifact'),
/** Plugin/driver requirements baked at compile time. */
manifest: EnvironmentArtifactManifestSchema,
/** Out-of-band payload reference (reserved). */
payloadRef: EnvironmentArtifactPayloadRefSchema.optional(),
})
.describe('ObjectStack Environment Artifact envelope (v0)');
export type EnvironmentArtifact = z.infer<typeof EnvironmentArtifactSchema>;
export type EnvironmentArtifactInput = z.input<typeof EnvironmentArtifactSchema>;