@xemahq/dsl 0.8.0 → 0.8.2

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.
Files changed (64) hide show
  1. package/dist/payload-codec/lib/blob-store.js +0 -0
  2. package/dist/payload-codec/lib/blob-store.js.map +1 -1
  3. package/package.json +3 -4
  4. package/src/deliverable-spec/index.ts +0 -19
  5. package/src/deliverable-spec/lib/schema.ts +0 -270
  6. package/src/deliverable-spec/lib/types.ts +0 -26
  7. package/src/payload-codec/index.ts +0 -44
  8. package/src/payload-codec/lib/blob-store.ts +0 -0
  9. package/src/payload-codec/lib/codec-context.ts +0 -38
  10. package/src/payload-codec/lib/codec.ts +0 -605
  11. package/src/payload-codec/lib/enums.ts +0 -58
  12. package/src/payload-codec/lib/errors.ts +0 -54
  13. package/src/payload-codec/lib/http-blob-store.ts +0 -267
  14. package/src/payload-codec/lib/lru-cache.ts +0 -81
  15. package/src/payload-codec/lib/payload.ts +0 -26
  16. package/src/payload-codec/temporal/index.ts +0 -36
  17. package/src/workflow/index.ts +0 -108
  18. package/src/workflow/lib/action-input-validator.ts +0 -160
  19. package/src/workflow/lib/compiler/action-shape.ts +0 -71
  20. package/src/workflow/lib/compiler/canonical-json.ts +0 -66
  21. package/src/workflow/lib/compiler/compile.ts +0 -1742
  22. package/src/workflow/lib/compiler/concurrency.ts +0 -223
  23. package/src/workflow/lib/compiler/dag.ts +0 -108
  24. package/src/workflow/lib/compiler/gate-defaults.ts +0 -153
  25. package/src/workflow/lib/compiler/index.ts +0 -11
  26. package/src/workflow/lib/compiler/inputs.ts +0 -254
  27. package/src/workflow/lib/compiler/installation-resource-validator.ts +0 -114
  28. package/src/workflow/lib/compiler/manifest-source.ts +0 -71
  29. package/src/workflow/lib/compiler/matrix.ts +0 -135
  30. package/src/workflow/lib/compiler/mount-plan.ts +0 -190
  31. package/src/workflow/lib/compiler/payload-reach-in.ts +0 -497
  32. package/src/workflow/lib/compiler/permissions.ts +0 -64
  33. package/src/workflow/lib/compiler/retry-timeout.ts +0 -105
  34. package/src/workflow/lib/compiler/review-step.ts +0 -548
  35. package/src/workflow/lib/compiler/types.ts +0 -172
  36. package/src/workflow/lib/compiler/variable-requirements.ts +0 -208
  37. package/src/workflow/lib/deliverable-spec-introspection-error.ts +0 -63
  38. package/src/workflow/lib/deliverable-spec-keys.ts +0 -147
  39. package/src/workflow/lib/deliverable-spec-source-scan.ts +0 -280
  40. package/src/workflow/lib/dispatch-inputs/index.ts +0 -160
  41. package/src/workflow/lib/dispatch-inputs/to-json-schema.ts +0 -60
  42. package/src/workflow/lib/duration.ts +0 -43
  43. package/src/workflow/lib/errors.ts +0 -37
  44. package/src/workflow/lib/expression/ast.ts +0 -108
  45. package/src/workflow/lib/expression/context.ts +0 -148
  46. package/src/workflow/lib/expression/evaluator.ts +0 -492
  47. package/src/workflow/lib/expression/index.ts +0 -28
  48. package/src/workflow/lib/expression/interpolation.ts +0 -84
  49. package/src/workflow/lib/expression/parser.ts +0 -264
  50. package/src/workflow/lib/expression/template.ts +0 -117
  51. package/src/workflow/lib/expression/tokenizer.ts +0 -200
  52. package/src/workflow/lib/expression/tokens.ts +0 -30
  53. package/src/workflow/lib/expression/walk-artifact-refs.ts +0 -232
  54. package/src/workflow/lib/installation-resource-kind.ts +0 -107
  55. package/src/workflow/lib/schemas-loader.ts +0 -64
  56. package/src/workflow/lib/serializer.ts +0 -30
  57. package/src/workflow/lib/types.ts +0 -417
  58. package/src/workflow/lib/validate.ts +0 -199
  59. package/src/workspace-manifest/index.ts +0 -27
  60. package/src/workspace-manifest/lib/compile.ts +0 -619
  61. package/src/workspace-manifest/lib/interpolate.ts +0 -166
  62. package/src/workspace-manifest/lib/resolve-extends.ts +0 -260
  63. package/src/workspace-manifest/lib/schema.ts +0 -692
  64. package/src/workspace-manifest/lib/types.ts +0 -446
@@ -1,692 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- import {
4
- AGENT_RUN_ROLES,
5
- type AgentRunRole,
6
- CREDENTIAL_KINDS,
7
- type CredentialBackendKind,
8
- MANIFEST_GIT_PUSH_BRANCH_SUFFIXES,
9
- MANIFEST_GIT_PUSH_CONCURRENCY_MODES,
10
- MANIFEST_SURFACES,
11
- type ManifestGitPushBranchSuffix,
12
- type ManifestGitPushConcurrencyMode,
13
- type ManifestSurface,
14
- ModelClassSchema,
15
- PromptMergeModeSchema,
16
- OUTPUT_SURFACE_KINDS,
17
- type OutputSurfaceKind,
18
- TOOLSET_KEYS,
19
- type ToolsetKey,
20
- } from '@xemahq/kernel-contracts/workflow';
21
-
22
- // ═══════════════════════════════════════════════════════════════════════════
23
- // ── Zod schema for AgentWorkspaceSpec YAML ──
24
- //
25
- // Mirrors the TypeScript types in `./types.ts`. Used by `compileManifest`
26
- // for validation; the compiler surfaces issues as `ManifestIssue[]`
27
- // (inside `CompileResult`) so the Monaco editor can render inline diagnostics.
28
- // ═══════════════════════════════════════════════════════════════════════════
29
-
30
- const SlugRegex = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
31
- const VersionRegex = /^\d+\.\d+\.\d+$/;
32
-
33
- /**
34
- * Upper bound on authored node-level `instructions` (author a
35
- * migrated agent as `base agent slug + instructions`). A prompt fragment
36
- * layered onto the referenced agent definition's intrinsic system prompt —
37
- * the same lever as `AgentNode.instructions`. The cap is a sanity
38
- * guard against accidentally pasting an entire document in; it is enforced
39
- * fail-fast at compile time, never silently truncated. Sized to admit the
40
- * largest first-party domain prompt migrated onto a base kernel (the
41
- * Webapp Studio coordinator, ~11.3k chars) without inviting whole-document
42
- * pastes.
43
- */
44
- export const MAX_AGENT_INSTRUCTIONS_LENGTH = 12_000;
45
-
46
- const ManifestInstructionsSchema = z
47
- .string()
48
- .min(1, 'instructions must not be empty when present')
49
- .max(
50
- MAX_AGENT_INSTRUCTIONS_LENGTH,
51
- `instructions must not exceed ${MAX_AGENT_INSTRUCTIONS_LENGTH} characters`,
52
- );
53
-
54
- export const ManifestInputDeclarationSchema = z.object({
55
- type: z.enum(['string', 'string[]', 'boolean', 'number', 'object[]']),
56
- required: z.boolean().optional(),
57
- /**
58
- * Default value. The accepted union mirrors `type`:
59
- * - `string` → string
60
- * - `number` → number
61
- * - `boolean` → boolean
62
- * - `string[]` → readonly string[]
63
- * - `object[]` → readonly { [k: string]: unknown }[] (structured
64
- * list-of-objects; renderers consume the shape
65
- * the producing manifest declares — the DSL does
66
- * not enforce per-object key validation.)
67
- */
68
- default: z
69
- .union([
70
- z.string(),
71
- z.number(),
72
- z.boolean(),
73
- z.array(z.string()),
74
- z.array(z.record(z.string(), z.unknown())),
75
- ])
76
- .optional(),
77
- enum: z.array(z.string()).optional(),
78
- description: z.string().optional(),
79
- });
80
-
81
- /**
82
- * Mount declaration: either a literal boolean (true=enable with defaults,
83
- * false=disable inherited mount), OR a strict object with a closed set of
84
- * configuration keys.
85
- *
86
- * Previously this was `z.record(z.string(), z.unknown())` which let any
87
- * key/value through and made the renderer the de-facto validator. CLAUDE
88
- * .md requires enums/closed sets at the boundary, so the object form is
89
- * `.strict()` and rejects unknown keys at compile time.
90
- */
91
- export const ManifestMountConfigSchema = z
92
- .object({
93
- mode: z.enum(['read-only', 'read-write']).optional(),
94
- /** Override the mount source path (relative to the upstream root). */
95
- source: z.string().min(1).optional(),
96
- /** Override the path the mount appears at inside the workspace. */
97
- path: z.string().min(1).optional(),
98
- })
99
- .strict();
100
-
101
- export const ManifestMountDeclarationSchema = z.union([
102
- z.boolean(),
103
- ManifestMountConfigSchema,
104
- ]);
105
-
106
- /**
107
- * Primitive-only value type for seed-file `vars`. Vars flow into
108
- * Handlebars rendering; non-primitive values are a footgun (`.toString()`
109
- * on `[object Object]`) and special keys like `__proto__` /
110
- * `constructor` / `prototype` can pollute the template context, so we
111
- * reject those at the schema layer.
112
- */
113
- const SafeVarKeyRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
114
- const RESERVED_VAR_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
115
- export const ManifestVarsSchema = z
116
- .record(
117
- z
118
- .string()
119
- .regex(SafeVarKeyRegex, 'var name must match /^[a-zA-Z_][a-zA-Z0-9_]*$/'),
120
- z.union([z.string(), z.number(), z.boolean()]),
121
- )
122
- .refine(
123
- (vars) => Object.keys(vars).every((k) => !RESERVED_VAR_KEYS.has(k)),
124
- {
125
- message:
126
- 'seed-file vars cannot use reserved keys (__proto__, constructor, prototype)',
127
- },
128
- );
129
-
130
- /**
131
- * Zod enum for AgentRunRole, sourced from the canonical kernel array.
132
- * The cast to `readonly [AgentRunRole, ...AgentRunRole[]]` preserves
133
- * the string-literal union under z.infer, so consumers (compileManifest
134
- * → CompiledWorkspaceManifest.agent.role) get a strongly-typed value
135
- * back, not a generic `string`.
136
- */
137
- const AgentRunRoleEnumSchema = z.enum(
138
- AGENT_RUN_ROLES as readonly [AgentRunRole, ...AgentRunRole[]],
139
- );
140
-
141
- /**
142
- * Discriminated `ModelRef` for manifest-level model recommendations.
143
- * Closed `kind` values match `@xemahq/workflow-contracts:ModelRef` and
144
- * the `xema/agent` action manifest's `model` property.
145
- */
146
- export const ManifestModelRefSchema = z.discriminatedUnion('kind', [
147
- z.object({
148
- kind: z.literal('concrete'),
149
- modelId: z.string().min(1),
150
- providerSlug: z.string().min(1).optional(),
151
- temperature: z.number().min(0).max(2).optional(),
152
- }),
153
- z.object({
154
- kind: z.literal('strategy'),
155
- modelClass: ModelClassSchema,
156
- temperature: z.number().min(0).max(2).optional(),
157
- }),
158
- ]);
159
-
160
- /**
161
- * Per-agent OpenCode-style permission map, mirroring the kernel
162
- * `PermissionMap` (`@xemahq/kernel-contracts/agent-workspace`). A node/agent
163
- * block declares this to specialize a generic base agent into a bounded
164
- * role (read-only reviewer, no-network explorer, scoped `task` allowlist)
165
- * WITHOUT authoring a new agent definition. Each entry is either a scalar
166
- * action or a per-target map (e.g. `bash: { '*': allow, 'rm -rf *': deny }`,
167
- * `task: { '*': deny, kb-researcher: allow }`). Keys are open (closed-set
168
- * permission keys + catch-all MCP/glob names), exactly as the kernel
169
- * resolver accepts — `resolveAgentPermissions` merges this over the system
170
- * tiers, and `assertNoRequiredPermissionsDenied` is the write-time guard.
171
- */
172
- const ManifestPermissionActionSchema = z.enum(['allow', 'ask', 'deny']);
173
- const ManifestPermissionRuleSchema = z.union([
174
- ManifestPermissionActionSchema,
175
- z.record(z.string().min(1), ManifestPermissionActionSchema),
176
- ]);
177
- export const ManifestAgentPermissionMapSchema = z.record(
178
- z.string().min(1),
179
- ManifestPermissionRuleSchema,
180
- );
181
-
182
- export const ManifestSubAgentSchema = z
183
- .object({
184
- slug: z
185
- .string()
186
- .min(1)
187
- .regex(
188
- /^[a-z][a-z0-9-]*$/,
189
- 'sub-agent slug must match /^[a-z][a-z0-9-]*$/',
190
- ),
191
- alias: z.string().max(64).optional(),
192
- defaultModel: ManifestModelRefSchema.optional(),
193
- instructions: ManifestInstructionsSchema.optional(),
194
- promptMode: PromptMergeModeSchema.optional(),
195
- permission: ManifestAgentPermissionMapSchema.optional(),
196
- })
197
- .strict();
198
-
199
- export const ManifestAgentBlockSchema = z
200
- .object({
201
- slug: z.string().min(1),
202
- stage: z.string().min(1),
203
- /**
204
- * The agent's role on the surface this manifest provisions. Closed
205
- * enum — the canonical list lives in `@xemahq/kernel-contracts/workflow`'s
206
- * `AGENT_RUN_ROLES`. Manifest upserts that name a role outside the
207
- * list fail at compile time with a typed `ManifestIssue`.
208
- */
209
- role: AgentRunRoleEnumSchema,
210
- deliverableSpecRef: z.string().optional(),
211
- defaultModel: ManifestModelRefSchema.optional(),
212
- instructions: ManifestInstructionsSchema.optional(),
213
- promptMode: PromptMergeModeSchema.optional(),
214
- permission: ManifestAgentPermissionMapSchema.optional(),
215
- subAgents: z.array(ManifestSubAgentSchema).optional(),
216
- })
217
- .strict()
218
- .refine(
219
- // No-self-delegation: the primary agent may not bind itself as a sub-agent
220
- // under the SAME identity (a no-op delegation loop). A composition node MAY
221
- // reuse the primary's agent kernel as a delegate when it carries a DISTINCT
222
- // `alias` — that is a separate, specialized delegate node (e.g. an
223
- // `architecture` delegate built on the `planner` kernel), not a self-loop.
224
- // So reject only when a sub-agent shares the primary slug AND has no
225
- // distinguishing alias.
226
- (a) =>
227
- !a.subAgents ||
228
- a.subAgents.every(
229
- (s) =>
230
- s.slug !== a.slug || (s.alias !== undefined && s.alias !== a.slug),
231
- ),
232
- {
233
- message:
234
- 'manifest agent block cannot bind itself as a sub-agent under the same ' +
235
- 'identity (no self-delegation); reuse the primary agent as a delegate ' +
236
- 'only under a distinct `alias`',
237
- },
238
- );
239
-
240
- /**
241
- * Closed-set enum schemas built from the canonical kernel arrays. The
242
- * cast to `[T, ...T[]]` preserves the literal-union under z.infer so
243
- * compileManifest's output stays strongly typed.
244
- */
245
- const ManifestSurfaceEnumSchema = z.enum(
246
- MANIFEST_SURFACES as readonly [ManifestSurface, ...ManifestSurface[]],
247
- );
248
-
249
- const ToolsetKeyEnumSchema = z.enum(
250
- TOOLSET_KEYS as readonly [ToolsetKey, ...ToolsetKey[]],
251
- );
252
-
253
- const CredentialBackendKindEnumSchema = z.enum(
254
- CREDENTIAL_KINDS as readonly [CredentialBackendKind, ...CredentialBackendKind[]],
255
- );
256
-
257
- const OutputSurfaceKindEnumSchema = z.enum(
258
- OUTPUT_SURFACE_KINDS as readonly [OutputSurfaceKind, ...OutputSurfaceKind[]],
259
- );
260
-
261
- /**
262
- * Generic slug used by skill refs. Looser than the manifest slug, and allows
263
- * the `/` path separator because skill bundles are HIERARCHICAL — skill-registry
264
- * stores slugs as path joins (e.g. `software-engineering/api/designing-apis`),
265
- * and resolution is exact-match on that slug. A flattened form would dangle.
266
- */
267
- const RefSlugRegex = /^[a-z0-9][a-z0-9._/-]*$/;
268
- const SemverRangeRegex = /^[~^]?\d+(\.\d+(\.\d+)?)?(-[a-zA-Z0-9.-]+)?$/;
269
-
270
- export const ManifestSkillRefSchema = z
271
- .object({
272
- slug: z.string().regex(RefSlugRegex, 'skill slug must match /^[a-z0-9][a-z0-9._/-]*$/ (hierarchical paths allowed)'),
273
- version: z.string().regex(SemverRangeRegex, 'version must be a semver pin or range').optional(),
274
- })
275
- .strict();
276
-
277
- export const ManifestCredentialSchema = z
278
- .object({
279
- name: z
280
- .string()
281
- .min(1)
282
- .regex(/^[A-Z_][A-Z0-9_]*$/, 'credential names must be UPPER_SNAKE_CASE'),
283
- kind: CredentialBackendKindEnumSchema,
284
- sourceRef: z.string().min(1),
285
- required: z.boolean().optional(),
286
- })
287
- .strict();
288
-
289
- export const ManifestPermissionsSchema = z
290
- .object({
291
- tools: z
292
- .object({
293
- allow: z.array(ToolsetKeyEnumSchema).optional(),
294
- deny: z.array(ToolsetKeyEnumSchema).optional(),
295
- })
296
- .strict(),
297
- })
298
- .strict();
299
-
300
- const PersistencePathRegex = /^[a-zA-Z0-9_.][a-zA-Z0-9_./-]*$/;
301
-
302
- const GitPushBranchSuffixEnumSchema = z.enum(
303
- MANIFEST_GIT_PUSH_BRANCH_SUFFIXES as readonly [
304
- ManifestGitPushBranchSuffix,
305
- ...ManifestGitPushBranchSuffix[],
306
- ],
307
- );
308
-
309
- const GitPushConcurrencyModeEnumSchema = z.enum(
310
- MANIFEST_GIT_PUSH_CONCURRENCY_MODES as readonly [
311
- ManifestGitPushConcurrencyMode,
312
- ...ManifestGitPushConcurrencyMode[],
313
- ],
314
- );
315
-
316
- /**
317
- * Per-slot persistence policy override. Only `git-push` is overridable
318
- * today because `tarball`/`none` have no fields a biome needs to tune.
319
- * Schema is `.strict()` so a typo in a closed-set value fails compile.
320
- */
321
- export const ManifestSlotPersistenceOverrideSchema = z
322
- .object({
323
- kind: z.literal('git-push'),
324
- branchPrefix: z
325
- .string()
326
- .min(1)
327
- .regex(
328
- /^[a-zA-Z0-9][a-zA-Z0-9._\-/]*\/$/,
329
- 'branchPrefix must end with `/` and contain only [A-Za-z0-9._-/]',
330
- ),
331
- branchSuffix: GitPushBranchSuffixEnumSchema.optional(),
332
- concurrencyMode: GitPushConcurrencyModeEnumSchema.optional(),
333
- mergeTarget: z
334
- .string()
335
- .min(1)
336
- .regex(
337
- /^[a-zA-Z0-9][a-zA-Z0-9._\-/]*$/,
338
- 'mergeTarget must be a git-safe branch name ([A-Za-z0-9._-/], no leading slash)',
339
- )
340
- .refine((v) => !v.includes('..'), {
341
- message: 'mergeTarget must not contain `..` segments',
342
- })
343
- .optional(),
344
- })
345
- .strict();
346
-
347
- /**
348
- * Keys allowed inside `persistence.overrides`. Closed to the slot keys
349
- * that have a non-`none` persistence policy in the AWP kernel — today
350
- * just `repos` (git-push) and `tmp` / `uploads` / `deliverables`
351
- * (tarball, no overridable fields, so still rejected at runtime via
352
- * the `kind: 'git-push'` literal). The compiler additionally validates
353
- * the key is git-push-persisted on the resolved spec.
354
- */
355
- const PersistenceOverrideKeyRegex = /^[a-z][a-z0-9-]*$/;
356
-
357
- export const ManifestPersistenceSchema = z
358
- .object({
359
- paths: z
360
- .array(
361
- z
362
- .string()
363
- .min(1)
364
- .regex(PersistencePathRegex, 'persistence path must be /workspace/-relative')
365
- .refine((p) => !p.includes('..'), {
366
- message: "persistence path must not contain '..' segments",
367
- })
368
- .refine((p) => !p.startsWith('/'), {
369
- message: 'persistence path must not start with /',
370
- }),
371
- )
372
- .min(1),
373
- /**
374
- * Per-slot persistence policy overrides. Replaces the AWP kernel
375
- * default for the named slot at session lifecycle time. The compile
376
- * step additionally enforces that each key names a slot whose
377
- * default policy is `git-push` — overriding a `tarball` or `none`
378
- * slot is nonsense and fails fast.
379
- */
380
- overrides: z
381
- .record(
382
- z.string().regex(PersistenceOverrideKeyRegex, 'slot key must be kebab-case'),
383
- ManifestSlotPersistenceOverrideSchema,
384
- )
385
- .optional(),
386
- })
387
- .strict();
388
-
389
- const StaticRootPathRegex = /^[a-zA-Z0-9_.][a-zA-Z0-9_./-]*$/;
390
-
391
- export const ManifestOutputSurfaceSchema = z
392
- .object({
393
- kind: OutputSurfaceKindEnumSchema,
394
- port: z.number().int().min(1).max(65535).optional(),
395
- healthPath: z
396
- .string()
397
- .min(1)
398
- .regex(/^\//, 'healthPath must start with /')
399
- .optional(),
400
- autoOpen: z.boolean().optional(),
401
- /**
402
- * Single-port (legacy) vs. multi-app output surface. `single` is
403
- * the historical behaviour and the default — one output surface
404
- * process per session, pinned to `port`. `multi` flips workspace-proxy's
405
- * output-surface supervisor into per-app mode: it discovers app targets
406
- * (from `.xema/output-surface.yaml` or autodetection), allocates a port
407
- * per app, and emits one gateway route per app. When `mode` is
408
- * `multi`, the top-level `port` is ignored and may be omitted.
409
- */
410
- mode: z.enum(['single', 'multi']).optional(),
411
- /**
412
- * Workspace-relative directory served by the baked
413
- * `static-artifact-server.mjs`. REQUIRED when `kind === 'static'`
414
- * and REJECTED for any other kind.
415
- */
416
- root: z
417
- .string()
418
- .min(1)
419
- .regex(StaticRootPathRegex, 'root must be a /workspace/-relative path')
420
- .refine((p) => !p.includes('..'), {
421
- message: "root must not contain '..' segments",
422
- })
423
- .refine((p) => !p.startsWith('/'), {
424
- message: 'root must not start with /',
425
- })
426
- .optional(),
427
- /**
428
- * Default document name for directory requests under a `static`
429
- * surface (e.g. `index.html`). Only meaningful when
430
- * `kind === 'static'`.
431
- */
432
- defaultDocument: z
433
- .string()
434
- .min(1)
435
- .regex(/^[a-zA-Z0-9_.][a-zA-Z0-9_.-]*$/, 'defaultDocument must be a plain filename')
436
- .optional(),
437
- })
438
- .strict()
439
- .superRefine((p, ctx) => {
440
- if (p.kind === 'static') {
441
- if (p.root === undefined) {
442
- ctx.addIssue({
443
- code: z.ZodIssueCode.custom,
444
- message: "outputSurface.root is required when kind === 'static'",
445
- path: ['root'],
446
- });
447
- }
448
- if (p.port !== undefined) {
449
- ctx.addIssue({
450
- code: z.ZodIssueCode.custom,
451
- message:
452
- "outputSurface.port is not allowed when kind === 'static' — the supervisor assigns the port",
453
- path: ['port'],
454
- });
455
- }
456
- if (p.mode !== undefined && p.mode !== 'single') {
457
- ctx.addIssue({
458
- code: z.ZodIssueCode.custom,
459
- message: "outputSurface.mode must be 'single' (or omitted) when kind === 'static'",
460
- path: ['mode'],
461
- });
462
- }
463
- return;
464
- }
465
- if (p.root !== undefined) {
466
- ctx.addIssue({
467
- code: z.ZodIssueCode.custom,
468
- message: `outputSurface.root is only valid when kind === 'static' (got kind="${p.kind}")`,
469
- path: ['root'],
470
- });
471
- }
472
- if (p.defaultDocument !== undefined) {
473
- ctx.addIssue({
474
- code: z.ZodIssueCode.custom,
475
- message: `outputSurface.defaultDocument is only valid when kind === 'static' (got kind="${p.kind}")`,
476
- path: ['defaultDocument'],
477
- });
478
- }
479
- if (p.kind !== 'none' && p.mode !== 'multi' && p.port === undefined) {
480
- ctx.addIssue({
481
- code: z.ZodIssueCode.custom,
482
- message:
483
- "outputSurface.port is required when kind !== 'none' and mode !== 'multi'",
484
- path: ['port'],
485
- });
486
- }
487
- });
488
-
489
- export const ManifestDisplayMetadataSchema = z
490
- .object({
491
- title: z.string().min(1).optional(),
492
- blurb: z.string().min(1).optional(),
493
- icon: z.string().min(1).optional(),
494
- category: z.string().min(1).optional(),
495
- badges: z.array(z.string().min(1)).optional(),
496
- sortOrder: z.number().int().optional(),
497
- hidden: z.boolean().optional(),
498
- ctaText: z.string().min(1).optional(),
499
- curated: z.boolean().optional(),
500
- })
501
- .strict();
502
-
503
- export const ManifestSeedFileSchema = z
504
- .object({
505
- path: z.string().min(1),
506
- slot: z.string().optional(),
507
- template: z.string().optional(),
508
- content: z.string().optional(),
509
- vars: ManifestVarsSchema.optional(),
510
- })
511
- .refine(
512
- (sf) => sf.template !== undefined || sf.content !== undefined,
513
- { message: 'seedFile must declare either `template` or `content`' },
514
- )
515
- .refine(
516
- (sf) => !(sf.template !== undefined && sf.content !== undefined),
517
- { message: 'seedFile cannot declare both `template` and `content`' },
518
- );
519
-
520
- export const ManifestEnvVarSchema = z.object({
521
- name: z
522
- .string()
523
- .min(1)
524
- .regex(/^[A-Z_][A-Z0-9_]*$/, 'env var names must be UPPER_SNAKE_CASE'),
525
- value: z.string(),
526
- });
527
-
528
- /**
529
- * Working-file declaration (`spec.workingFiles[]`). Closed-set `format` /
530
- * `syncDirection` enums; `path` is workspace-relative; `slug` is the
531
- * stable agent-facing identifier; `sourceKind` is an open snake_case
532
- * string registered with the worker's working-file engine; `sourceRef`
533
- * is a kind-specific string map (values may carry `${input.x}`).
534
- */
535
- const WORKING_FILE_PATH_RE = /^[a-zA-Z0-9_.][a-zA-Z0-9_./-]*$/;
536
- const WORKING_FILE_SLUG_RE = /^[a-z0-9][a-z0-9._-]*$/;
537
- const WORKING_FILE_SOURCE_KIND_RE = /^[a-z][a-z0-9-]*(?:_[a-z0-9-]+)*$/;
538
-
539
- /**
540
- * Strip every `${input.x}` template token before applying a regex check.
541
- * `workingFiles[].slug` and `workingFiles[].path` legitimately carry
542
- * input-template tokens (one document buddy session per KB page, with
543
- * `slug = ${input.pageSlug}`), and the post-`extends:` schema pass runs
544
- * BEFORE input interpolation — so the validator MUST tolerate templates
545
- * on the same characters the regex enforces on the resolved value.
546
- *
547
- * After interpolation the workspace pipeline re-validates the resolved
548
- * literal, so loosening the schema here does not weaken the contract;
549
- * it just moves the literal-shape check past the substitution boundary.
550
- */
551
- const INPUT_TOKEN_RE = /\$\{\s*input\.[a-zA-Z_]\w*\s*\}/g;
552
- function stripInputTokens(value: string): string {
553
- return value.replace(INPUT_TOKEN_RE, '');
554
- }
555
-
556
- export const ManifestWorkingFileSchema = z
557
- .object({
558
- slug: z
559
- .string()
560
- .min(1)
561
- .refine(
562
- (s) => WORKING_FILE_SLUG_RE.test(stripInputTokens(s) || 'a'),
563
- 'slug must match /^[a-z0-9][a-z0-9._-]*$/ (input-template tokens permitted)',
564
- ),
565
- path: z
566
- .string()
567
- .min(1)
568
- .refine(
569
- (p) => WORKING_FILE_PATH_RE.test(stripInputTokens(p) || 'a'),
570
- 'path must be a /workspace/-relative slash path (input-template tokens permitted)',
571
- )
572
- .refine((p) => !p.includes('..'), {
573
- message: "path must not contain '..' segments",
574
- })
575
- .refine((p) => !p.startsWith('/'), {
576
- message: 'path must not start with /',
577
- }),
578
- format: z.enum(['markdown', 'html', 'json', 'yaml', 'text']),
579
- syncDirection: z.enum(['down-only', 'up-only', 'bidirectional']),
580
- sourceKind: z
581
- .string()
582
- .min(1)
583
- .regex(
584
- WORKING_FILE_SOURCE_KIND_RE,
585
- 'sourceKind must be kebab-or-snake case (e.g. kb-page)',
586
- ),
587
- sourceRef: z.record(z.string().min(1), z.string()),
588
- })
589
- .strict();
590
-
591
- /**
592
- * Default tool selection inherited by every session/run booted on
593
- * this workspace. Shape matches the kernel `ToolSelectionEntry` from
594
- * `@xemahq/kernel-contracts/mcp-tool`. Resolved at boot via mcp-gateway-api
595
- * `POST /mcp-resolve` and dropped into `opencode.json:mcp`. Sessions
596
- * may override per-instance via `PATCH /sessions/:id/tools`.
597
- */
598
- export const ManifestToolSelectionEntrySchema = z.discriminatedUnion('kind', [
599
- z
600
- .object({
601
- kind: z.literal('provider'),
602
- providerKind: z.enum([
603
- 'mcp_server',
604
- 'catalog',
605
- 'biome_workflow_tools',
606
- 'biome_code_tools',
607
- ]),
608
- resourceId: z.string().min(1).max(256),
609
- })
610
- .strict(),
611
- z
612
- .object({
613
- kind: z.literal('tool'),
614
- providerKind: z.enum([
615
- 'mcp_server',
616
- 'catalog',
617
- 'biome_workflow_tools',
618
- 'biome_code_tools',
619
- ]),
620
- resourceId: z.string().min(1).max(256),
621
- toolName: z.string().min(1).max(256),
622
- })
623
- .strict(),
624
- ]);
625
-
626
- export const WorkspaceManifestSpecSchema = z.object({
627
- inputs: z.record(z.string(), ManifestInputDeclarationSchema).optional(),
628
- mounts: z.record(z.string(), ManifestMountDeclarationSchema).optional(),
629
- agent: ManifestAgentBlockSchema,
630
- skills: z.array(ManifestSkillRefSchema).optional(),
631
- /**
632
- * Default MCP tool selection for sessions/runs booted on this manifest.
633
- * Resolved at boot via mcp-gateway-api `POST /mcp-resolve` and merged
634
- * into `opencode.json:mcp`.
635
- */
636
- toolSelection: z.array(ManifestToolSelectionEntrySchema).max(128).optional(),
637
- credentials: z.array(ManifestCredentialSchema).optional(),
638
- permissions: ManifestPermissionsSchema.optional(),
639
- persistence: ManifestPersistenceSchema.optional(),
640
- outputSurface: ManifestOutputSurfaceSchema.optional(),
641
- seedFiles: z.array(ManifestSeedFileSchema).optional(),
642
- env: z.array(ManifestEnvVarSchema).optional(),
643
- workingFiles: z.array(ManifestWorkingFileSchema).max(32).optional(),
644
- });
645
-
646
- export const WorkspaceManifestMetadataSchema = z.object({
647
- slug: z.string().regex(SlugRegex, 'slug must be lowercase kebab-case'),
648
- version: z.string().regex(VersionRegex, 'version must follow semver MAJOR.MINOR.PATCH'),
649
- org: z.string().optional(),
650
- description: z.string().optional(),
651
- /**
652
- * Optional in the YAML for additive rollout; the compiler defaults
653
- * absent values to `[workflow, agent-session]` so existing
654
- * manifests keep compiling. New biome/org manifests SHOULD declare
655
- * this explicitly — a later step sweeps the shipped manifests.
656
- */
657
- surfaceCompat: z
658
- .array(ManifestSurfaceEnumSchema)
659
- .min(1, 'metadata.surfaceCompat must list at least one surface')
660
- .optional(),
661
- display: ManifestDisplayMetadataSchema.optional(),
662
- });
663
-
664
- /**
665
- * `extends:` URI shape — `xema://manifest/<slug>@<version>` (version
666
- * required; resolver pins to a specific row at compile time so old
667
- * runs stay deterministic). Validation is lenient on slug chars
668
- * because biome / org templates may use richer naming than the
669
- * kernel-shipped slugs.
670
- */
671
- const ManifestExtendsRegex = /^xema:\/\/manifest\/[a-z0-9][a-z0-9._-]*@\d+\.\d+\.\d+$/;
672
-
673
- export const WorkspaceManifestSchema = z
674
- .object({
675
- apiVersion: z.literal('xema.dev/workspace/v1'),
676
- kind: z.literal('WorkspaceManifest'),
677
- extends: z
678
- .string()
679
- .regex(
680
- ManifestExtendsRegex,
681
- 'extends must look like `xema://manifest/<slug>@<MAJOR.MINOR.PATCH>`',
682
- )
683
- .optional(),
684
- metadata: WorkspaceManifestMetadataSchema,
685
- spec: WorkspaceManifestSpecSchema,
686
- })
687
- .meta({
688
- id: 'https://xema.dev/schemas/workspace/v1/WorkspaceManifest.json',
689
- title: 'Xema WorkspaceManifest',
690
- description:
691
- 'Declarative shape of /workspace/ for an agent invocation: mounts, agent block, seed files, env vars.',
692
- });