@xemahq/kernel-contracts 0.7.0 → 0.9.0
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-composition/index.d.ts +3 -3
- package/dist/agent-composition/index.d.ts.map +1 -1
- package/dist/agent-composition/index.js +3 -3
- package/dist/agent-composition/index.js.map +1 -1
- package/dist/agent-composition/lib/agent-limits-schema.d.ts +4 -0
- package/dist/agent-composition/lib/agent-limits-schema.d.ts.map +1 -0
- package/dist/agent-composition/lib/agent-limits-schema.js +13 -0
- package/dist/agent-composition/lib/agent-limits-schema.js.map +1 -0
- package/dist/agent-composition/lib/agent-workspace-config.d.ts +37 -0
- package/dist/agent-composition/lib/agent-workspace-config.d.ts.map +1 -0
- package/dist/agent-composition/lib/agent-workspace-config.js +9 -0
- package/dist/agent-composition/lib/agent-workspace-config.js.map +1 -0
- package/dist/agent-composition/lib/agent.d.ts +65 -0
- package/dist/agent-composition/lib/agent.d.ts.map +1 -0
- package/dist/agent-composition/lib/agent.js +18 -0
- package/dist/agent-composition/lib/agent.js.map +1 -0
- package/dist/agent-workspace/awp-spec.json +1 -1
- package/dist/biome/lib/biome-manifest.d.ts +880 -27
- package/dist/biome/lib/biome-manifest.d.ts.map +1 -1
- package/dist/biome/lib/biome-manifest.js +507 -20
- package/dist/biome/lib/biome-manifest.js.map +1 -1
- package/dist/distribution/lib/distribution-selector.d.ts +2 -0
- package/dist/distribution/lib/distribution-selector.d.ts.map +1 -1
- package/dist/distribution/lib/distribution-selector.js +6 -2
- package/dist/distribution/lib/distribution-selector.js.map +1 -1
- package/dist/resource/index.d.ts +7 -0
- package/dist/resource/index.d.ts.map +1 -0
- package/dist/resource/index.js +23 -0
- package/dist/resource/index.js.map +1 -0
- package/dist/resource/lib/plan.d.ts +31 -0
- package/dist/resource/lib/plan.d.ts.map +1 -0
- package/dist/resource/lib/plan.js +32 -0
- package/dist/resource/lib/plan.js.map +1 -0
- package/dist/resource/lib/resource-declaration.d.ts +15 -0
- package/dist/resource/lib/resource-declaration.d.ts.map +1 -0
- package/dist/resource/lib/resource-declaration.js +32 -0
- package/dist/resource/lib/resource-declaration.js.map +1 -0
- package/dist/resource/lib/resource-kind.d.ts +20 -0
- package/dist/resource/lib/resource-kind.d.ts.map +1 -0
- package/dist/resource/lib/resource-kind.js +24 -0
- package/dist/resource/lib/resource-kind.js.map +1 -0
- package/dist/resource/lib/resource-managed-by.d.ts +9 -0
- package/dist/resource/lib/resource-managed-by.d.ts.map +1 -0
- package/dist/resource/lib/resource-managed-by.js +13 -0
- package/dist/resource/lib/resource-managed-by.js.map +1 -0
- package/dist/resource/lib/stack.d.ts +22 -0
- package/dist/resource/lib/stack.d.ts.map +1 -0
- package/dist/resource/lib/stack.js +23 -0
- package/dist/resource/lib/stack.js.map +1 -0
- package/dist/resource/lib/xema-manifest.d.ts +11 -0
- package/dist/resource/lib/xema-manifest.d.ts.map +1 -0
- package/dist/resource/lib/xema-manifest.js +47 -0
- package/dist/resource/lib/xema-manifest.js.map +1 -0
- package/package.json +1 -1
- package/src/agent-composition/index.ts +3 -3
- package/src/agent-composition/lib/{composition-limits-schema.ts → agent-limits-schema.ts} +1 -1
- package/src/agent-composition/lib/{composition.ts → agent.ts} +1 -1
- package/src/biome/lib/biome-manifest.ts +1140 -66
- package/src/distribution/lib/distribution-selector.ts +16 -4
- package/src/invocation/lib/invoke-request.ts +1 -1
- package/src/invocation/lib/invoke-response.ts +1 -1
- package/src/resource/index.ts +22 -0
- package/src/resource/lib/plan.ts +83 -0
- package/src/resource/lib/resource-declaration.ts +67 -0
- package/src/resource/lib/resource-kind.ts +37 -0
- package/src/resource/lib/resource-managed-by.ts +29 -0
- package/src/resource/lib/stack.ts +61 -0
- package/src/resource/lib/xema-manifest.ts +86 -0
- /package/src/agent-composition/lib/{composition-workspace.ts → agent-workspace-config.ts} +0 -0
|
@@ -1,39 +1,58 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from '../../
|
|
2
|
+
CAPABILITY_SLUG_PATTERN,
|
|
3
|
+
FilterExprSchema,
|
|
4
|
+
} from '../../connector';
|
|
5
|
+
import { ContributionKindSchema } from '../../contribution';
|
|
6
|
+
import { CapabilityRefSchema } from '../../capability';
|
|
6
7
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import { BiomeEnginesSchema, type BiomeEngines } from './biome-engines';
|
|
8
|
+
ProvisioningGuardSchema,
|
|
9
|
+
ProvisioningStepKindSchema,
|
|
10
|
+
ProvisioningTriggerSchema,
|
|
11
|
+
} from '../../provisioning';
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from '
|
|
13
|
+
MigrationRunnerKind,
|
|
14
|
+
OrgDatabasePurpose,
|
|
15
|
+
} from '../../org-database';
|
|
16
|
+
import {
|
|
17
|
+
DataLocalitySchema,
|
|
18
|
+
RuntimeIsolationLevelSchema,
|
|
19
|
+
RunnerTrustTier,
|
|
20
|
+
} from '../../runner';
|
|
21
|
+
import { z } from 'zod';
|
|
22
|
+
import {
|
|
23
|
+
BiomeAudience,
|
|
24
|
+
BiomeAudienceSchema,
|
|
25
|
+
DEFAULT_BIOME_AUDIENCE,
|
|
26
|
+
} from './biome-audience';
|
|
27
|
+
import {
|
|
28
|
+
BiomeEnginesSchema,
|
|
29
|
+
} from './biome-engines';
|
|
16
30
|
import {
|
|
17
31
|
BiomeLifecycleHooksSchema,
|
|
18
|
-
type BiomeLifecycleHooks,
|
|
19
32
|
} from './biome-lifecycle-hooks';
|
|
20
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
BiomePermissionsManifestSchema,
|
|
35
|
+
} from './biome-permissions';
|
|
21
36
|
|
|
22
37
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
38
|
+
* `xema-biome.json` — declarative description of a Xema biome's
|
|
39
|
+
* identity and the contributions it ships. The schema is a discriminated
|
|
40
|
+
* union on `xema.target`.
|
|
41
|
+
*
|
|
42
|
+
* This is the SINGLE canonical biome-manifest schema for the platform: the
|
|
43
|
+
* `{ name, version, xema: { … } }` wrapped shape. The legacy flat
|
|
44
|
+
* `{ schemaVersion, id, name, … }` shape was deleted — there is no dual
|
|
45
|
+
* parser and no backward-compat shim. `@xemahq/biome-sdk/host` re-exports
|
|
46
|
+
* everything in this module; it owns no second copy.
|
|
27
47
|
*/
|
|
28
|
-
export const BIOME_MANIFEST_FILENAME = 'xema-biome.json';
|
|
29
48
|
|
|
30
49
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
50
|
+
* Canonical filename of the biome manifest at the root of every biome
|
|
51
|
+
* source package. The platform validates and parses ONLY this file's
|
|
52
|
+
* top-level `{ name, version, xema }` shape; everything else in the
|
|
53
|
+
* bundle is free-form.
|
|
35
54
|
*/
|
|
36
|
-
export
|
|
55
|
+
export const BIOME_MANIFEST_FILENAME = 'xema-biome.json';
|
|
37
56
|
|
|
38
57
|
/**
|
|
39
58
|
* Declarative event subscription entry of the biome manifest
|
|
@@ -53,50 +72,1105 @@ export const BiomeEventSubscriptionSchema = z.object({
|
|
|
53
72
|
}) as z.ZodType<BiomeEventSubscription>;
|
|
54
73
|
|
|
55
74
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
* Manifest-shape biome SCOPE — the dependency/boot tier a biome declares in
|
|
76
|
+
* its `xema.scope` field. Value-identical to the kernel `BiomeTier` set
|
|
77
|
+
* (`kernel|system|base|platform`). Kept as a manifest-internal schema (not
|
|
78
|
+
* re-exported) so it does not collide with the kernel `BiomeScope`
|
|
79
|
+
* (installation scope) / `BiomeTier` (dependency tier) enums that the rest
|
|
80
|
+
* of `@xemahq/kernel-contracts/biome` already exposes.
|
|
81
|
+
*
|
|
82
|
+
* Biome tier (Layered Biome Architecture — "Linux-faithful" model):
|
|
83
|
+
* - `kernel` — L1 universal, mandatory. Boots before any other biome.
|
|
84
|
+
* - `system` — L2 system biomes (systemd / dbus / sshd analogues).
|
|
85
|
+
* - `base` — L2.5 foundational user-facing primitives.
|
|
86
|
+
* - `platform` — L3 first-party domain + integration biomes.
|
|
87
|
+
*/
|
|
88
|
+
const ManifestBiomeScopeSchema = z.enum([
|
|
89
|
+
'kernel',
|
|
90
|
+
'system',
|
|
91
|
+
'base',
|
|
92
|
+
'platform',
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The Contribution Protocol (`@xemahq/kernel-contracts/contribution`) is the
|
|
97
|
+
* single authoritative enum for kinds a biome may ship.
|
|
98
|
+
*
|
|
99
|
+
* Conventional on-disk directory name per `ships.content[]` / `ships.modules[]`
|
|
100
|
+
* string. Used ONLY for the `ships`-based file-walk seeders that have not yet
|
|
101
|
+
* migrated to the `contributions/` directory protocol. New code MUST use the
|
|
102
|
+
* `xema.contributions` block (or inline contributions) instead of
|
|
103
|
+
* `ships.content` / `ships.modules`.
|
|
104
|
+
*
|
|
105
|
+
* Keys are the camelCase tokens accepted in `xema-biome.json`; the mapping
|
|
106
|
+
* returns `null` for any key it does not recognise so callers can fail fast.
|
|
107
|
+
*/
|
|
108
|
+
const LEGACY_SHIPS_CONTENT_DIR: Readonly<Record<string, string>> = {
|
|
109
|
+
agents: 'agents',
|
|
110
|
+
workflowConfig: 'workflow-config',
|
|
111
|
+
deliverableSpecs: 'deliverable-specs',
|
|
112
|
+
workspaceManifests: 'workspace-manifests',
|
|
113
|
+
workspaceManifestTemplates: 'workspace-manifest-templates',
|
|
114
|
+
toolProfiles: 'tool-profiles',
|
|
115
|
+
mcpCatalog: 'mcp-catalog',
|
|
116
|
+
mcpTools: 'mcp-tools',
|
|
117
|
+
openCodeSkills: 'skills',
|
|
118
|
+
openCodeTools: 'opencode-tools',
|
|
119
|
+
openCodePlugins: 'opencode-plugins',
|
|
120
|
+
roleCapabilities: 'role-capabilities',
|
|
121
|
+
artifactTypes: 'artifact-types',
|
|
122
|
+
biomeInstallSchema: 'install-schema',
|
|
123
|
+
icons: 'icons',
|
|
124
|
+
projectKits: 'project-kits',
|
|
125
|
+
provisioningScaffolds: 'provisioning',
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const LEGACY_SHIPS_MODULES_DIR: Readonly<Record<string, string>> = {
|
|
129
|
+
agents: 'agents',
|
|
130
|
+
actions: 'actions',
|
|
131
|
+
mountSourceKinds: 'mount-source-kinds',
|
|
132
|
+
deliverableSpecKinds: 'deliverable-spec-kinds',
|
|
133
|
+
runtimeMountKinds: 'runtime-mount-kinds',
|
|
134
|
+
workspaceSpecOverlay: 'workspace-spec-overlay',
|
|
135
|
+
systemOverlayContributions: 'system-overlay-contributions',
|
|
136
|
+
adapterKinds: 'adapter-kinds',
|
|
137
|
+
integrationProviders: 'integration-providers',
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Resolve a legacy `ships.content[]` value to its conventional directory
|
|
142
|
+
* name, or `null` for unknown tokens. Used by the legacy file-walk
|
|
143
|
+
* catalog/seeders; new code should use the contributions protocol.
|
|
144
|
+
*/
|
|
145
|
+
export function contentKindToDir(kind: string): string {
|
|
146
|
+
const dir = LEGACY_SHIPS_CONTENT_DIR[kind];
|
|
147
|
+
if (!dir) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`biome-host-sdk: unknown legacy ships.content[] value "${kind}". ` +
|
|
150
|
+
`Either migrate the biome to xema.contributions or extend LEGACY_SHIPS_CONTENT_DIR.`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return dir;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Resolve a legacy `ships.modules[]` value to its conventional directory
|
|
158
|
+
* name. Same legacy-only scope as `contentKindToDir`.
|
|
159
|
+
*/
|
|
160
|
+
export function moduleKindToDir(kind: string): string {
|
|
161
|
+
const dir = LEGACY_SHIPS_MODULES_DIR[kind];
|
|
162
|
+
if (!dir) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`biome-host-sdk: unknown legacy ships.modules[] value "${kind}". ` +
|
|
165
|
+
`Either migrate the biome to xema.contributions or extend LEGACY_SHIPS_MODULES_DIR.`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return dir;
|
|
80
169
|
}
|
|
81
170
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Org-managed database declaration for biomes that need a dedicated
|
|
173
|
+
* schema in a provisioned org database.
|
|
174
|
+
*/
|
|
175
|
+
export const BiomeDatabaseDeclarationSchema = z
|
|
176
|
+
.object({
|
|
177
|
+
purpose: z.literal(OrgDatabasePurpose.Biome),
|
|
178
|
+
runnerKind: z.literal(MigrationRunnerKind.Prisma),
|
|
179
|
+
})
|
|
180
|
+
.strict();
|
|
181
|
+
export type BiomeDatabaseDeclaration = z.infer<typeof BiomeDatabaseDeclarationSchema>;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Per-API declaration shipped under `xema.ships.apis[]` (Phase 9).
|
|
185
|
+
* Each entry becomes one subdomain (`<name>.api.<base-domain>`)
|
|
186
|
+
* served by a biome-shipped NestJS service. biome-host-api applies
|
|
187
|
+
* the Helm sub-chart at install time and writes a route-registry
|
|
188
|
+
* entry the ingress shim consults. Validated structurally here;
|
|
189
|
+
* detailed contract semantics live in `@xemahq/biome-api-sdk`.
|
|
190
|
+
*/
|
|
191
|
+
export const BiomeApiDeclarationSchema = z
|
|
192
|
+
.object({
|
|
193
|
+
name: z
|
|
194
|
+
.string()
|
|
195
|
+
.min(1)
|
|
196
|
+
.regex(
|
|
197
|
+
/^[a-z][a-z0-9-]*$/,
|
|
198
|
+
'API name must be lowercase kebab-case; becomes the subdomain slug',
|
|
199
|
+
),
|
|
200
|
+
basePath: z
|
|
201
|
+
.string()
|
|
202
|
+
.regex(/^\/.*$/)
|
|
203
|
+
.optional(),
|
|
204
|
+
/** Workspace package name + port — the deployment image. */
|
|
205
|
+
image: z
|
|
206
|
+
.object({
|
|
207
|
+
package: z.string().min(1),
|
|
208
|
+
port: z.number().int().min(1).max(65_535),
|
|
209
|
+
})
|
|
210
|
+
.strict(),
|
|
211
|
+
openapiSpec: z.string().optional(),
|
|
212
|
+
scopes: z.array(z.enum(['public', 'org', 'project', 'installation'])).min(1),
|
|
213
|
+
/**
|
|
214
|
+
* Relative directory (from the manifest) of this API's source tree, e.g.
|
|
215
|
+
* `./api/mailops-api`. Used by `tooling/codegen/generate-service-bootstrap.mjs`
|
|
216
|
+
* to locate where to emit the `<name>.bootstrap.generated.ts` descriptor.
|
|
217
|
+
*/
|
|
218
|
+
path: z.string().min(1).optional(),
|
|
219
|
+
/**
|
|
220
|
+
* Service-bootstrap SSOT fields (consumed by `XemaServiceModule.forBiome`
|
|
221
|
+
* via the generated `BiomeServiceDescriptor`). Declaring them HERE — once,
|
|
222
|
+
* in the manifest — is what removes the duplicate string literals from each
|
|
223
|
+
* service's `app.module.ts`. All optional with safe codegen defaults.
|
|
224
|
+
*/
|
|
225
|
+
displayName: z.string().min(1).optional(),
|
|
226
|
+
serviceKind: z
|
|
227
|
+
.enum(['platform-service', 'biome-api', 'cli'])
|
|
228
|
+
.optional(),
|
|
229
|
+
/** Registry names this API calls (→ descriptor.requiresServices + consumes). */
|
|
230
|
+
requiresServices: z.array(z.string().min(1)).optional(),
|
|
231
|
+
/** Capability refs this API exposes (→ descriptor.exposesCapabilities). */
|
|
232
|
+
exposesCapabilities: z.array(z.string().min(1)).optional(),
|
|
233
|
+
})
|
|
234
|
+
.strict();
|
|
235
|
+
export type BiomeApiDeclaration = z.infer<typeof BiomeApiDeclarationSchema>;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Phase-C Contribution Protocol entry. The body of an
|
|
239
|
+
* `*.contribution.json` file under a biome's `contributions/` directory,
|
|
240
|
+
* or an inline entry under `xema.contributions.inline[]` in
|
|
241
|
+
* `xema-biome.json`.
|
|
242
|
+
*
|
|
243
|
+
* Validated structurally here (kind + slug + opaque manifest). The
|
|
244
|
+
* authoritative per-kind manifest validation lives in the kind-specific
|
|
245
|
+
* contracts package and is exercised at boot by the generic
|
|
246
|
+
* `BootstrapContributionsService` template (see
|
|
247
|
+
* `bootstrap-contributions-service.ts`).
|
|
248
|
+
*
|
|
249
|
+
* See `.claude/plans/following-up-the-xema-os-plan-md-abstract-stream.md` §6.C.1.
|
|
250
|
+
*/
|
|
251
|
+
export const BiomeContributionEntrySchema = z
|
|
252
|
+
.object({
|
|
253
|
+
kind: ContributionKindSchema,
|
|
254
|
+
/**
|
|
255
|
+
* Kind-local slug; uniqueness is per `(kind, biomeId)`. The full
|
|
256
|
+
* `<namespace>/<local-slug>` slug expected by the kernel
|
|
257
|
+
* `ContributionSlugSchema` is composed at install time by the biome
|
|
258
|
+
* host (`namespace = biomeId`).
|
|
259
|
+
*/
|
|
260
|
+
id: z
|
|
261
|
+
.string()
|
|
262
|
+
.min(1)
|
|
263
|
+
.regex(
|
|
264
|
+
/^[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9][a-z0-9._-]*)*$/,
|
|
265
|
+
'Contribution id must be lowercase kebab/dot-case, optionally with nested `/` segments',
|
|
266
|
+
),
|
|
267
|
+
manifest: z.unknown(),
|
|
268
|
+
})
|
|
269
|
+
.strict();
|
|
270
|
+
export type BiomeContributionEntry = z.infer<typeof BiomeContributionEntrySchema>;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Per-biome Contribution Protocol declaration. Either points at a
|
|
274
|
+
* `contributions/` directory containing one `*.contribution.json` per
|
|
275
|
+
* entry, OR carries the entries inline. Both forms are allowed in one
|
|
276
|
+
* manifest — directory entries are walked first, inline entries layered
|
|
277
|
+
* on top with last-write-wins on `(kind, id)` collision (the host fails
|
|
278
|
+
* fast on duplicates at boot).
|
|
279
|
+
*
|
|
280
|
+
* Phase C plan-of-record: §6.C.1.
|
|
281
|
+
*/
|
|
282
|
+
export const BiomeContributionsSchema = z
|
|
283
|
+
.object({
|
|
284
|
+
/**
|
|
285
|
+
* Directory under the biome root (relative path). Defaults to
|
|
286
|
+
* `./contributions` when omitted but the block is present. The host
|
|
287
|
+
* silently ignores a missing directory — empty contribution sets are
|
|
288
|
+
* legal.
|
|
289
|
+
*/
|
|
290
|
+
directory: z.string().min(1).optional(),
|
|
291
|
+
/** Inline contribution entries. */
|
|
292
|
+
inline: z.array(BiomeContributionEntrySchema).optional(),
|
|
293
|
+
})
|
|
294
|
+
.strict();
|
|
295
|
+
export type BiomeContributions = z.infer<typeof BiomeContributionsSchema>;
|
|
296
|
+
|
|
297
|
+
export const BiomeShipsSchema = z
|
|
298
|
+
.object({
|
|
299
|
+
/**
|
|
300
|
+
* @deprecated PHASE-C-SWEEP: superseded by top-level
|
|
301
|
+
* `xema.contributions`. Kept as a free-form string array until the
|
|
302
|
+
* codemod sweep migrates every biome to the Contribution Protocol.
|
|
303
|
+
* Values are matched against `ContributionKind` at registration time.
|
|
304
|
+
*/
|
|
305
|
+
content: z.array(z.string().min(1)).optional(),
|
|
306
|
+
/**
|
|
307
|
+
* @deprecated PHASE-C-SWEEP: superseded by top-level
|
|
308
|
+
* `xema.contributions`. See `content` above — same rationale.
|
|
309
|
+
*/
|
|
310
|
+
modules: z.array(z.string().min(1)).optional(),
|
|
311
|
+
apis: z.array(BiomeApiDeclarationSchema).optional(),
|
|
312
|
+
})
|
|
313
|
+
.strict();
|
|
314
|
+
export type BiomeShips = z.infer<typeof BiomeShipsSchema>;
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Manifest-shape hosting/trust posture for a server biome. Used by
|
|
318
|
+
* integration-host plumbing (Phase 7) to decide whether to load
|
|
319
|
+
* adapter-contribution modules in-process or via a sidecar deployment.
|
|
320
|
+
* Manifests authored by Xema and audited third parties may set
|
|
321
|
+
* `first-party`; everything else defaults to `third-party`.
|
|
322
|
+
*
|
|
323
|
+
* Kept manifest-internal (not re-exported) so it does not collide with the
|
|
324
|
+
* kernel `BiomeTrustTier` provenance enum (`first-party | verified-store |
|
|
325
|
+
* community-store | …`) already exposed by this package.
|
|
326
|
+
*/
|
|
327
|
+
const ManifestBiomeTrustTierSchema = z.enum(['first-party', 'third-party']);
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* AdapterKind slug — accepts a built-in slug verbatim, plus any
|
|
331
|
+
* lowercase kebab-case slug a biome-contributed kind could register.
|
|
332
|
+
* The runtime registry in integration-adapters-api is the authority on
|
|
333
|
+
* which slugs actually resolve; manifest parsing only enforces shape.
|
|
334
|
+
*/
|
|
335
|
+
const AdapterKindRefSchema = z
|
|
336
|
+
.string()
|
|
337
|
+
.min(1)
|
|
338
|
+
.regex(/^[a-z][a-z0-9-]*$/, 'adapterKind must be lowercase kebab-case');
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Execution mode for a shipped agent. Mirrors the `mode:` field in the
|
|
342
|
+
* agent .md frontmatter (and the `executionMode` column in
|
|
343
|
+
* llm-registry's `agent_definitions` table). Closed enum — every agent
|
|
344
|
+
* is either the session's primary or a delegate available via the
|
|
345
|
+
* `task` tool.
|
|
346
|
+
*
|
|
347
|
+
* The on-disk convention for backwards compatibility is that a missing
|
|
348
|
+
* frontmatter `mode:` is treated as `primary`. The manifest declaration
|
|
349
|
+
* is the authoritative answer; the cross-validator
|
|
350
|
+
* (`crossValidateAgentDeclarations`) accepts a declared `primary` against
|
|
351
|
+
* a missing frontmatter mode but rejects any other mismatch.
|
|
352
|
+
*/
|
|
353
|
+
export const BiomeAgentModeSchema = z.enum(['primary', 'subagent']);
|
|
354
|
+
export type BiomeAgentMode = z.infer<typeof BiomeAgentModeSchema>;
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Declaration of a single agent shipped by a biome. Path is implicit:
|
|
358
|
+
* `<biomeRoot>/agents/<slug>.md` (the convention enforced by
|
|
359
|
+
* `enumerateContentAcrossBiomeTree`). The slug also becomes the
|
|
360
|
+
* agent's canonical id in llm-registry, so it must match the filename.
|
|
361
|
+
*
|
|
362
|
+
* `mode` is duplicated between the manifest and the .md frontmatter on
|
|
363
|
+
* purpose: the manifest field makes the agent roster greppable and
|
|
364
|
+
* lets biome-host refuse to boot on drift; the frontmatter is what
|
|
365
|
+
* the agent-definition seeder consumes when materialising the agent
|
|
366
|
+
* into the registry. The boot-time cross-validator ensures both agree.
|
|
367
|
+
*/
|
|
368
|
+
export const BiomeAgentDeclarationSchema = z
|
|
369
|
+
.object({
|
|
370
|
+
slug: z
|
|
371
|
+
.string()
|
|
372
|
+
.min(1)
|
|
373
|
+
.regex(
|
|
374
|
+
/^[a-z][a-z0-9-_]*$/,
|
|
375
|
+
'agent slug must be lowercase kebab/underscore-case and match the .md basename',
|
|
376
|
+
),
|
|
377
|
+
mode: BiomeAgentModeSchema,
|
|
378
|
+
})
|
|
379
|
+
.strict();
|
|
380
|
+
export type BiomeAgentDeclaration = z.infer<typeof BiomeAgentDeclarationSchema>;
|
|
381
|
+
|
|
382
|
+
const CapabilitySlugSchema = z
|
|
383
|
+
.string()
|
|
384
|
+
.min(1)
|
|
385
|
+
.regex(CAPABILITY_SLUG_PATTERN, 'capability slug must be <domain>.<verb-qualifier>');
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Declared integration dependency. The install wizard surfaces these
|
|
389
|
+
* as a consent screen; `biome-host-api` refuses to activate a
|
|
390
|
+
* `BiomeInstallation` whose non-optional requirements are not bound.
|
|
391
|
+
* `purpose` is mandatory (one short sentence) so users see *why* the
|
|
392
|
+
* biome needs the integration.
|
|
393
|
+
*/
|
|
394
|
+
export const IntegrationRequirementSchema = z
|
|
395
|
+
.object({
|
|
396
|
+
adapterKind: AdapterKindRefSchema,
|
|
397
|
+
optional: z.boolean().optional(),
|
|
398
|
+
purpose: z.string().min(1),
|
|
399
|
+
capabilities: z.array(CapabilitySlugSchema).min(1),
|
|
400
|
+
})
|
|
401
|
+
.strict();
|
|
402
|
+
export type IntegrationRequirement = z.infer<typeof IntegrationRequirementSchema>;
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Per-(workflow, event) filter expression. The workflow YAML's
|
|
406
|
+
* `on.webhook[].event` value must match `event` here; the workflow
|
|
407
|
+
* compiler enforces this at publish time. `workflowId` is the
|
|
408
|
+
* shipped-workflow id (the `id` field inside the YAML), not a runtime
|
|
409
|
+
* `WorkflowDefinition.id`. At install time `biome-host-api` resolves
|
|
410
|
+
* the bundle and copies the relevant entries onto the
|
|
411
|
+
* `WorkflowDefinition.biomeWebhookFilters` column so the dispatcher
|
|
412
|
+
* can evaluate without re-reading the manifest.
|
|
413
|
+
*/
|
|
414
|
+
export const WebhookFilterSchema = z
|
|
415
|
+
.object({
|
|
416
|
+
workflowId: z
|
|
417
|
+
.string()
|
|
418
|
+
.min(1)
|
|
419
|
+
.regex(/^[a-z][a-z0-9-]*$/, 'workflowId must be lowercase kebab-case'),
|
|
420
|
+
event: z.string().min(1),
|
|
421
|
+
/**
|
|
422
|
+
* Optional canonical entity kind the filter targets (e.g. `push`,
|
|
423
|
+
* `change_request`, `item`). When set, the manifest cross-validator
|
|
424
|
+
* narrows `$envelope.*` path checking to that entity kind's
|
|
425
|
+
* payload — typos against the specific kind fail fast at boot.
|
|
426
|
+
* Omitted = union path check across every entity kind under the
|
|
427
|
+
* referenced workflow's required adapter kinds (over-permissive
|
|
428
|
+
* but still catches the obvious typo class).
|
|
429
|
+
*/
|
|
430
|
+
entityKind: z.string().min(1).optional(),
|
|
431
|
+
predicate: FilterExprSchema,
|
|
432
|
+
})
|
|
433
|
+
.strict();
|
|
434
|
+
export type WebhookFilter = z.infer<typeof WebhookFilterSchema>;
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Server-side capability declarations. Validated at boot for shape +
|
|
438
|
+
* closed-set membership; runtime enforcement is Phase 4 (network
|
|
439
|
+
* allow-list, MCP ACL, secret scope). Declaring intent now means every
|
|
440
|
+
* biome already states it — no migration when enforcement turns on.
|
|
441
|
+
*/
|
|
442
|
+
export const ServerBiomeCapabilitiesSchema = z
|
|
443
|
+
.object({
|
|
444
|
+
mcp: z.array(z.string().min(1)).optional(),
|
|
445
|
+
network: z
|
|
446
|
+
.object({ allowList: z.array(z.string().min(1)) })
|
|
447
|
+
.strict()
|
|
448
|
+
.optional(),
|
|
449
|
+
secrets: z.array(z.string().min(1)).optional(),
|
|
450
|
+
})
|
|
451
|
+
.strict();
|
|
452
|
+
export type ServerBiomeCapabilities = z.infer<typeof ServerBiomeCapabilitiesSchema>;
|
|
453
|
+
|
|
454
|
+
/** Web-side capability declarations. Same Phase-4 timing as server. */
|
|
455
|
+
export const WebBiomeCapabilitiesSchema = z
|
|
456
|
+
.object({
|
|
457
|
+
slots: z.array(z.string().min(1)).optional(),
|
|
458
|
+
apiClients: z.array(z.string().min(1)).optional(),
|
|
459
|
+
})
|
|
460
|
+
.strict();
|
|
461
|
+
export type WebBiomeCapabilities = z.infer<typeof WebBiomeCapabilitiesSchema>;
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* How the `provisioning-plan-resolver` expands one scaffold declaration
|
|
465
|
+
* into resolved steps. Closed enum — `each-app-target` evaluates
|
|
466
|
+
* `selector.configPointer` against the session's `customConfig` and emits
|
|
467
|
+
* one step per matched array element (webapp-studio's `/appTargets`).
|
|
468
|
+
* Adding a strategy is a one-line schema change here plus a matching
|
|
469
|
+
* branch in the resolver.
|
|
470
|
+
*/
|
|
471
|
+
export const ProvisioningScaffoldMatchKindSchema = z.enum(['each-app-target']);
|
|
472
|
+
export type ProvisioningScaffoldMatchKind = z.infer<
|
|
473
|
+
typeof ProvisioningScaffoldMatchKindSchema
|
|
474
|
+
>;
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Declaration of one provisioning scaffold a biome ships. The scaffold
|
|
478
|
+
* *content* (command spec + template files) lives at
|
|
479
|
+
* `<biomeRoot>/provisioning/<id>.yaml`; this manifest entry is the
|
|
480
|
+
* lightweight "I ship this scaffold, and here is when/whether it runs"
|
|
481
|
+
* declaration the resolver reads.
|
|
482
|
+
*
|
|
483
|
+
* `selector` is what makes provisioning "run only if instructed" — a
|
|
484
|
+
* scaffold whose `configPointer` resolves to nothing in the session's
|
|
485
|
+
* `customConfig` never enters the plan.
|
|
486
|
+
*/
|
|
487
|
+
export const ProvisioningScaffoldDeclarationSchema = z
|
|
488
|
+
.object({
|
|
489
|
+
id: z
|
|
490
|
+
.string()
|
|
491
|
+
.min(1)
|
|
492
|
+
.regex(
|
|
493
|
+
/^[a-z][a-z0-9-]*$/,
|
|
494
|
+
'scaffold id must be lowercase kebab-case and match the provisioning/<id>.yaml basename',
|
|
495
|
+
),
|
|
496
|
+
kind: ProvisioningStepKindSchema,
|
|
497
|
+
triggers: z.array(ProvisioningTriggerSchema).min(1),
|
|
498
|
+
guard: ProvisioningGuardSchema,
|
|
499
|
+
selector: z
|
|
500
|
+
.object({
|
|
501
|
+
/**
|
|
502
|
+
* JSON Pointer (RFC 6901) into the session's `customConfig`. The
|
|
503
|
+
* resolver expands the scaffold only when this resolves; for
|
|
504
|
+
* `each-app-target` it must resolve to an array.
|
|
505
|
+
*/
|
|
506
|
+
configPointer: z.string().min(1),
|
|
507
|
+
matchKind: ProvisioningScaffoldMatchKindSchema,
|
|
508
|
+
})
|
|
509
|
+
.strict(),
|
|
510
|
+
})
|
|
511
|
+
.strict();
|
|
512
|
+
export type ProvisioningScaffoldDeclaration = z.infer<
|
|
513
|
+
typeof ProvisioningScaffoldDeclarationSchema
|
|
514
|
+
>;
|
|
515
|
+
|
|
516
|
+
const BiomeIdSchema = z
|
|
517
|
+
.string()
|
|
518
|
+
.min(1)
|
|
519
|
+
.regex(/^[a-z][a-z0-9-]*$/, 'biome id must be kebab-case');
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Per-handler declarative event subscription. Type-aliased to
|
|
523
|
+
* `BiomeEventSubscription` so the SDK's manifest field and the
|
|
524
|
+
* `xema-biome.json` parser share one shape. See plan §12.4.
|
|
525
|
+
*/
|
|
526
|
+
export type BiomeManifestSubscribe = BiomeEventSubscription;
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* `requires`: biome id → semver range. Same shape as the kernel biome
|
|
530
|
+
* manifest's `requires` block (plan §12.4); the platform refuses to
|
|
531
|
+
* enable a biome/biome whose `requires` cannot be satisfied by the
|
|
532
|
+
* other enabled biomes/biomes in the org.
|
|
533
|
+
*/
|
|
534
|
+
export type BiomeManifestRequires = Readonly<Record<string, string>>;
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Additive XSI fields layered onto the per-target `xema` block in Phase
|
|
538
|
+
* 1A (plan §17.2 item 2). Pure additive. All fields are OPTIONAL.
|
|
539
|
+
*
|
|
540
|
+
* - `subscribes[]` — declarative event wiring (§12.4). Each entry binds
|
|
541
|
+
* one CloudEvents `type` to a handler module path.
|
|
542
|
+
* - `requires` — typed semver dependency declaration on other
|
|
543
|
+
* biomes/biomes (§12.4).
|
|
544
|
+
* - `contributes[]` — closed `ContributionKind` enum from
|
|
545
|
+
* `@xemahq/kernel-contracts/contribution`. The biome-host uses this to know
|
|
546
|
+
* which slot registries to walk on install (§12.1).
|
|
547
|
+
* - `requiresCapabilities[]` — capability refs the biome needs the host
|
|
548
|
+
* to grant before any of its handlers/contributions execute (§30.2).
|
|
549
|
+
* - `exposesCapabilities[]` — capability refs the biome itself
|
|
550
|
+
* implements and offers to others (§30.2).
|
|
551
|
+
* - `permissions` — install-time consent metadata (default profile +
|
|
552
|
+
* per-ref hints + UX groupings) for `requiresCapabilities[]` (§30.2).
|
|
553
|
+
* - `lifecycle` — module paths the host invokes on
|
|
554
|
+
* install/upgrade/enable/disable/uninstall transitions (§24.5).
|
|
555
|
+
* - `engines.xema` — semver range the host enforces against the running
|
|
556
|
+
* platform version (§24.4).
|
|
557
|
+
*
|
|
558
|
+
* Kernel-owned types are referenced through the sibling biome contract
|
|
559
|
+
* modules — never redefined here.
|
|
560
|
+
*/
|
|
561
|
+
const XsiManifestExtensionsShape = {
|
|
562
|
+
subscribes: z.array(BiomeEventSubscriptionSchema).optional(),
|
|
93
563
|
requires: z.record(z.string().min(1), z.string().min(1)).optional(),
|
|
564
|
+
contributes: z.array(ContributionKindSchema).optional(),
|
|
565
|
+
/**
|
|
566
|
+
* Phase-C Contribution Protocol entries (the actual envelopes, not just
|
|
567
|
+
* the kind whitelist). Replaces the legacy `ships.content` +
|
|
568
|
+
* `ships.modules` per-kind file-walk seeders with a single generic
|
|
569
|
+
* `BootstrapContributionsService` template; see Phase C.1 of the
|
|
570
|
+
* plan-of-record. Either inline entries or a pointer to a
|
|
571
|
+
* `contributions/` directory at the biome root.
|
|
572
|
+
*/
|
|
573
|
+
contributions: BiomeContributionsSchema.optional(),
|
|
574
|
+
requiresCapabilities: z.array(CapabilityRefSchema).optional(),
|
|
575
|
+
exposesCapabilities: z.array(CapabilityRefSchema).optional(),
|
|
94
576
|
permissions: BiomePermissionsManifestSchema.optional(),
|
|
95
577
|
lifecycle: BiomeLifecycleHooksSchema.optional(),
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
578
|
+
engines: BiomeEnginesSchema.optional(),
|
|
579
|
+
/**
|
|
580
|
+
* WHO the biome is for. `org` (default) — a normal biome available to
|
|
581
|
+
* organizations and eligible for the store. `operator` — Xema platform
|
|
582
|
+
* operators only: distribution selectors skip it (it ships only when
|
|
583
|
+
* explicitly named), it is never store-listed, hidden from org UIs, and
|
|
584
|
+
* gated to `platform_admin`. See `BiomeAudience` in `./biome-audience`.
|
|
585
|
+
*/
|
|
586
|
+
audience: BiomeAudienceSchema.optional(),
|
|
587
|
+
/**
|
|
588
|
+
* Whether the biome appears in the store catalog. Defaults to `true` for
|
|
589
|
+
* `org`-audience biomes; an `operator` biome is NEVER store-listed
|
|
590
|
+
* regardless of this flag. Set `false` for a normal biome that should ship
|
|
591
|
+
* and be installable but stay out of the store (the lighter "hidden"
|
|
592
|
+
* option). Resolve via `isBiomeStoreListed` — never read raw.
|
|
593
|
+
*/
|
|
594
|
+
storeListed: z.boolean().optional(),
|
|
595
|
+
} as const;
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* `runtimeRequirements` — what a biome NEEDS from the runtime/runner that
|
|
599
|
+
* executes it. This is the *requirements* half of the scheduling contract:
|
|
600
|
+
* the package states constraints; the installer decides who installs it;
|
|
601
|
+
* the runtime binding decides where it may run; the router schedules onto a
|
|
602
|
+
* matching healthy runner.
|
|
603
|
+
*
|
|
604
|
+
* HARD RULE — requirements, NEVER placement. A manifest MAY declare
|
|
605
|
+
* required/preferred labels, resource hints, allowed isolation levels,
|
|
606
|
+
* allowed data localities, and a minimum runner trust tier. It MUST NOT
|
|
607
|
+
* name a concrete target: `runtimeId`, `runnerId`, `runnerName`, `host`,
|
|
608
|
+
* `serverUrl`, or any infra address. Those are placement decisions owned by
|
|
609
|
+
* the scheduler, never the package. Enforced two ways: this object is
|
|
610
|
+
* `.strict()` (an unknown key — including any placement field — fails the
|
|
611
|
+
* parse fast) AND `tooling/boundaries/check-biome-no-placement.mjs` scans
|
|
612
|
+
* the raw manifest for forbidden keys anywhere in the tree with an
|
|
613
|
+
* actionable message.
|
|
614
|
+
*
|
|
615
|
+
* Enums are reused from `@xemahq/kernel-contracts/runner` so the requirement side
|
|
616
|
+
* (this manifest) and the capability side (a runtime/runner descriptor)
|
|
617
|
+
* speak the same closed vocabulary and can never drift.
|
|
618
|
+
*/
|
|
619
|
+
const RuntimeRequirementsSchema = z
|
|
620
|
+
.object({
|
|
621
|
+
/**
|
|
622
|
+
* Capability/tag labels the executing runner must (`required`) or
|
|
623
|
+
* preferably should (`preferred`) advertise — e.g.
|
|
624
|
+
* `xema.runner/docker`, `xema.runner/gpu`, `region`. Matched against a
|
|
625
|
+
* runner's advertised `labels` by the router's selector.
|
|
626
|
+
*/
|
|
627
|
+
labels: z
|
|
628
|
+
.object({
|
|
629
|
+
required: z.array(z.string().min(1)).optional(),
|
|
630
|
+
preferred: z.array(z.string().min(1)).optional(),
|
|
631
|
+
})
|
|
632
|
+
.strict()
|
|
633
|
+
.optional(),
|
|
634
|
+
/**
|
|
635
|
+
* Coarse resource hints (Kubernetes-style quantity strings). Advisory
|
|
636
|
+
* scheduling input, not a hard guarantee at this phase.
|
|
637
|
+
*/
|
|
638
|
+
resources: z
|
|
639
|
+
.object({
|
|
640
|
+
cpu: z.string().min(1).optional(),
|
|
641
|
+
memory: z.string().min(1).optional(),
|
|
642
|
+
})
|
|
643
|
+
.strict()
|
|
644
|
+
.optional(),
|
|
645
|
+
/**
|
|
646
|
+
* Isolation boundaries the biome ACCEPTS. A runtime whose provided
|
|
647
|
+
* isolation is not in this set is ineligible. Omit to accept any.
|
|
648
|
+
*/
|
|
649
|
+
isolation: z
|
|
650
|
+
.object({
|
|
651
|
+
allowed: z.array(RuntimeIsolationLevelSchema).min(1),
|
|
652
|
+
})
|
|
653
|
+
.strict()
|
|
654
|
+
.optional(),
|
|
655
|
+
/**
|
|
656
|
+
* Data localities the biome ACCEPTS (e.g. pin to `customer-private`).
|
|
657
|
+
* A runtime/runner whose locality is not in this set is ineligible.
|
|
658
|
+
*/
|
|
659
|
+
locality: z
|
|
660
|
+
.object({
|
|
661
|
+
allowed: z.array(DataLocalitySchema).min(1),
|
|
662
|
+
})
|
|
663
|
+
.strict()
|
|
664
|
+
.optional(),
|
|
665
|
+
/**
|
|
666
|
+
* Minimum attestation trust tier the executing runner must meet.
|
|
667
|
+
*/
|
|
668
|
+
trustTier: z
|
|
669
|
+
.object({
|
|
670
|
+
minimum: z.nativeEnum(RunnerTrustTier),
|
|
671
|
+
})
|
|
672
|
+
.strict()
|
|
673
|
+
.optional(),
|
|
674
|
+
})
|
|
675
|
+
.strict();
|
|
676
|
+
|
|
677
|
+
export type RuntimeRequirements = z.infer<typeof RuntimeRequirementsSchema>;
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Optional presentation metadata a biome declares for host-shell surfaces
|
|
681
|
+
* (launchpad tiles, portal biome rails, the portal-admin biome picker). It is
|
|
682
|
+
* purely cosmetic — never consulted for resolution, enablement, or routing —
|
|
683
|
+
* so every field is optional and a host may fall back to its own defaults.
|
|
684
|
+
*
|
|
685
|
+
* - `icon` is a stable key the host resolves to a glyph (the closed set of
|
|
686
|
+
* renderable keys lives in the host shell, not here, so new biomes are never
|
|
687
|
+
* blocked by an enum bump).
|
|
688
|
+
* - `category` groups biomes in the picker/rail (e.g. "Finance"). It is also
|
|
689
|
+
* the seed source for distribution default portals.
|
|
690
|
+
* - `summary` is a short one-liner for tiles, distinct from the longer
|
|
691
|
+
* freeform `description`.
|
|
692
|
+
* - `accent` is an optional CSS color token for the biome's glyph/card.
|
|
693
|
+
*/
|
|
694
|
+
const BiomeDisplaySchema = z
|
|
695
|
+
.object({
|
|
696
|
+
icon: z.string().min(1).optional(),
|
|
697
|
+
category: z.string().min(1).optional(),
|
|
698
|
+
summary: z.string().min(1).optional(),
|
|
699
|
+
accent: z.string().min(1).optional(),
|
|
700
|
+
})
|
|
701
|
+
.strict();
|
|
702
|
+
|
|
703
|
+
export type BiomeDisplay = z.infer<typeof BiomeDisplaySchema>;
|
|
704
|
+
|
|
705
|
+
const ServerBiomeXemaSchema = z
|
|
706
|
+
.object({
|
|
707
|
+
id: BiomeIdSchema,
|
|
708
|
+
displayName: z.string().min(1),
|
|
709
|
+
description: z.string().optional(),
|
|
710
|
+
display: BiomeDisplaySchema.optional(),
|
|
711
|
+
scope: ManifestBiomeScopeSchema,
|
|
712
|
+
target: z.literal('server'),
|
|
713
|
+
/**
|
|
714
|
+
* What this biome needs from the runtime/runner that executes it
|
|
715
|
+
* (labels, resources, isolation, locality, trust). Requirements only —
|
|
716
|
+
* a manifest may NOT name a concrete runtime/runner/host (see
|
|
717
|
+
* {@link RuntimeRequirementsSchema}). Server biomes only; web biomes are
|
|
718
|
+
* static bundles served by the host shell and never run on a runner.
|
|
719
|
+
*/
|
|
720
|
+
runtimeRequirements: RuntimeRequirementsSchema.optional(),
|
|
721
|
+
/**
|
|
722
|
+
* Server biome ids this biome depends on. Biome-host topologically
|
|
723
|
+
* sorts the server set at boot and fails fast on cycles or unsatisfied
|
|
724
|
+
* dependencies. A kernel biome may only depend on other kernel
|
|
725
|
+
* biomes (enforced by biome-host).
|
|
726
|
+
*/
|
|
727
|
+
dependencies: z.array(BiomeIdSchema).optional(),
|
|
728
|
+
/**
|
|
729
|
+
* Biome id(s) this biome EXTENDS. An extending biome is the
|
|
730
|
+
* same-or-upgraded variant of the biome(s) it extends and MAY re-ship
|
|
731
|
+
* their content (agent slugs, workspace manifests, skills); when both
|
|
732
|
+
* are installed the extending biome's version takes precedence. This is
|
|
733
|
+
* the machine-readable form of the "the commercial cultivar extends this
|
|
734
|
+
* community reference biome" relationship — e.g. `document-buddy`
|
|
735
|
+
* (platform cultivar) extends `document-buddy-reference` (community
|
|
736
|
+
* reference). Distinct from `dependencies`: an extended biome need NOT be
|
|
737
|
+
* installed (the extension is self-contained), whereas a dependency must
|
|
738
|
+
* resolve at boot. `extends` is what lets the agent-slug uniqueness
|
|
739
|
+
* invariant tolerate the re-shipped slug — see
|
|
740
|
+
* tooling/boundaries/check-agent-duplication.mjs.
|
|
741
|
+
*/
|
|
742
|
+
extends: z.union([BiomeIdSchema, z.array(BiomeIdSchema)]).optional(),
|
|
743
|
+
ships: BiomeShipsSchema.optional(),
|
|
744
|
+
capabilities: ServerBiomeCapabilitiesSchema.optional(),
|
|
745
|
+
/**
|
|
746
|
+
* Hosting posture for adapter-contribution modules (Phase 7). Other
|
|
747
|
+
* biome kinds ignore this field. Defaults to `third-party`.
|
|
748
|
+
*/
|
|
749
|
+
trustTier: ManifestBiomeTrustTierSchema.optional(),
|
|
750
|
+
/**
|
|
751
|
+
* Integration dependencies the biome needs to be useful. Surfaced
|
|
752
|
+
* to the install wizard as a consent screen. Non-optional entries
|
|
753
|
+
* gate the `BiomeInstallation` state transition to `active`.
|
|
754
|
+
*/
|
|
755
|
+
integrationRequirements: z.array(IntegrationRequirementSchema).optional(),
|
|
756
|
+
/**
|
|
757
|
+
* Per-(workflow, event) filter expressions evaluated by the
|
|
758
|
+
* workflow-engine dispatcher at webhook time. Each entry MUST
|
|
759
|
+
* resolve to a workflow shipped by this biome and an event that
|
|
760
|
+
* workflow declares under `on.webhook[].event`. The DSL compiler
|
|
761
|
+
* cross-validates at publish time; manifest parsing only enforces
|
|
762
|
+
* shape.
|
|
763
|
+
*/
|
|
764
|
+
webhookFilters: z.array(WebhookFilterSchema).optional(),
|
|
765
|
+
/**
|
|
766
|
+
* Workflows the biome wants exposed as MCP tools. The MCP gateway
|
|
767
|
+
* (`mcp-gateway-api`) projects each entry into the catalog-mode
|
|
768
|
+
* tool listing surfaced to agents via the agent runtime. When an agent
|
|
769
|
+
* invokes the tool, the gateway dispatches the workflow run
|
|
770
|
+
* (auto-attaching the calling installation's id, JWT-derived org
|
|
771
|
+
* + actor context) and returns the named deliverable plus an
|
|
772
|
+
* optional workspace mount entry so the result lands on disk
|
|
773
|
+
* automatically.
|
|
774
|
+
*
|
|
775
|
+
* Each `key` is unique within the biome and becomes the MCP tool
|
|
776
|
+
* id (`<biomeId>__<key>`). `workflowSlug` MUST resolve to a
|
|
777
|
+
* workflow this biome ships; the workflow-engine cross-validates
|
|
778
|
+
* at publish time. `outputProjection.deliverable.slug` picks one
|
|
779
|
+
* of the workflow's declared `produces:` entries to surface as the
|
|
780
|
+
* tool's primary content payload. The optional `mount` block
|
|
781
|
+
* issues a `POST /workspace/mounts/apply` on the calling agent's
|
|
782
|
+
* session so the result file appears under `/workspace/<slot>/...`
|
|
783
|
+
* — seamless from the agent's point of view.
|
|
784
|
+
*/
|
|
785
|
+
mcpWorkflowTools: z
|
|
786
|
+
.array(
|
|
787
|
+
z
|
|
788
|
+
.object({
|
|
789
|
+
key: z
|
|
790
|
+
.string()
|
|
791
|
+
.min(1)
|
|
792
|
+
.regex(/^[a-z][a-z0-9-]*$/, 'key must be lowercase-kebab'),
|
|
793
|
+
workflowSlug: z.string().min(1),
|
|
794
|
+
displayName: z.string().min(1),
|
|
795
|
+
description: z.string().min(1),
|
|
796
|
+
outputProjection: z
|
|
797
|
+
.object({
|
|
798
|
+
kind: z.literal('deliverable'),
|
|
799
|
+
slug: z.string().min(1),
|
|
800
|
+
})
|
|
801
|
+
.strict(),
|
|
802
|
+
mount: z
|
|
803
|
+
.object({
|
|
804
|
+
slot: z.enum(['inputs', 'references', 'deliverables']),
|
|
805
|
+
as: z.string().min(1),
|
|
806
|
+
})
|
|
807
|
+
.strict()
|
|
808
|
+
.optional(),
|
|
809
|
+
})
|
|
810
|
+
.strict(),
|
|
811
|
+
)
|
|
812
|
+
.optional(),
|
|
813
|
+
/**
|
|
814
|
+
* Plain handler functions the biome's API service exposes as MCP
|
|
815
|
+
* tools. The platform wraps them via the `biome_code_tools`
|
|
816
|
+
* provider — biome authors never see MCP protocol bytes.
|
|
817
|
+
*
|
|
818
|
+
* Each entry's `key` is unique within the biome and maps to a
|
|
819
|
+
* decorated method on the biome's API service via the
|
|
820
|
+
* `@BiomeCodeTool` decorator from `@xemahq/biome-api-sdk`. The
|
|
821
|
+
* full descriptor (inputSchema, displayName, output kind) is
|
|
822
|
+
* authoritative on the biome API side and surfaced through
|
|
823
|
+
* `GET /code-tools-manifest`; this manifest entry is the
|
|
824
|
+
* lightweight "I ship this tool" declaration the platform reads
|
|
825
|
+
* at install time.
|
|
826
|
+
*/
|
|
827
|
+
mcpTools: z
|
|
828
|
+
.array(
|
|
829
|
+
z
|
|
830
|
+
.object({
|
|
831
|
+
key: z
|
|
832
|
+
.string()
|
|
833
|
+
.min(1)
|
|
834
|
+
.regex(/^[a-z][a-z0-9-]*$/, 'key must be lowercase-kebab'),
|
|
835
|
+
handler: z
|
|
836
|
+
.object({
|
|
837
|
+
kind: z.literal('biome_api'),
|
|
838
|
+
method: z.enum(['POST']).default('POST'),
|
|
839
|
+
})
|
|
840
|
+
.strict(),
|
|
841
|
+
})
|
|
842
|
+
.strict(),
|
|
843
|
+
)
|
|
844
|
+
.optional(),
|
|
845
|
+
/**
|
|
846
|
+
* Tools the biome recommends are made available when a user
|
|
847
|
+
* adopts it. Surfaced in the install UX as "this biome recommends
|
|
848
|
+
* these tools" — NOT auto-injected into every session. Each entry
|
|
849
|
+
* matches the kernel `ToolSelectionEntry` shape from
|
|
850
|
+
* `@xemahq/kernel-contracts/mcp-tool`. The frontend picker drops these
|
|
851
|
+
* directly into the session's selection on user opt-in.
|
|
852
|
+
*
|
|
853
|
+
* Validation: shape-only at boot. `resourceId` references resolve
|
|
854
|
+
* at install time (e.g. biome-installation-id is materialized by
|
|
855
|
+
* biome-host-api when the installation is created). Cross-tenant
|
|
856
|
+
* misuse is blocked downstream by the resolver (Layer 2) and the
|
|
857
|
+
* PATCH-time validator (Layer 1) when the user actually adopts the
|
|
858
|
+
* selection.
|
|
859
|
+
*/
|
|
860
|
+
defaultToolSelection: z
|
|
861
|
+
.array(
|
|
862
|
+
z
|
|
863
|
+
.discriminatedUnion('kind', [
|
|
864
|
+
z
|
|
865
|
+
.object({
|
|
866
|
+
kind: z.literal('provider'),
|
|
867
|
+
providerKind: z.enum([
|
|
868
|
+
'mcp_server',
|
|
869
|
+
'catalog',
|
|
870
|
+
'biome_workflow_tools',
|
|
871
|
+
'biome_code_tools',
|
|
872
|
+
]),
|
|
873
|
+
resourceId: z.string().min(1).max(256),
|
|
874
|
+
})
|
|
875
|
+
.strict(),
|
|
876
|
+
z
|
|
877
|
+
.object({
|
|
878
|
+
kind: z.literal('tool'),
|
|
879
|
+
providerKind: z.enum([
|
|
880
|
+
'mcp_server',
|
|
881
|
+
'catalog',
|
|
882
|
+
'biome_workflow_tools',
|
|
883
|
+
'biome_code_tools',
|
|
884
|
+
]),
|
|
885
|
+
resourceId: z.string().min(1).max(256),
|
|
886
|
+
toolName: z.string().min(1).max(256),
|
|
887
|
+
})
|
|
888
|
+
.strict(),
|
|
889
|
+
]),
|
|
890
|
+
)
|
|
891
|
+
.max(64)
|
|
892
|
+
.optional(),
|
|
893
|
+
/**
|
|
894
|
+
* Explicit roster of agents this biome ships. Required when
|
|
895
|
+
* `ships.content` includes `'agents'`; forbidden otherwise. Each
|
|
896
|
+
* entry pins one `agents/<slug>.md` file with its execution mode
|
|
897
|
+
* (primary vs subagent).
|
|
898
|
+
*
|
|
899
|
+
* Why both this field AND the on-disk `agents/` scan exist:
|
|
900
|
+
* - The declaration makes the roster greppable from outside the
|
|
901
|
+
* biome tree (`grep '"slug": "build"' biomes/*\/xema-biome.json`).
|
|
902
|
+
* - It lets biome-host fail fast at boot when an agent file is
|
|
903
|
+
* added or removed without updating the manifest — the prior
|
|
904
|
+
* convention silently accepted drift.
|
|
905
|
+
* - The closed `mode` enum surfaces primary-vs-subagent intent at
|
|
906
|
+
* install time instead of requiring readers to parse YAML
|
|
907
|
+
* frontmatter from every .md.
|
|
908
|
+
*
|
|
909
|
+
* Cross-validated by `crossValidateAgentDeclarations` at
|
|
910
|
+
* biome-host boot; the seeder still reads frontmatter for the rest
|
|
911
|
+
* of each agent's attributes.
|
|
912
|
+
*/
|
|
913
|
+
agents: z.array(BiomeAgentDeclarationSchema).optional(),
|
|
914
|
+
/**
|
|
915
|
+
* Workspace-provisioning scaffolds the biome ships (Epic A). Each
|
|
916
|
+
* entry pins one `provisioning/<id>.yaml` recipe and declares when it
|
|
917
|
+
* runs + how the resolver expands it. Required ⟺ `ships.content`
|
|
918
|
+
* includes `provisioningScaffolds`; cross-validated in `superRefine`.
|
|
919
|
+
*/
|
|
920
|
+
provisioning: z.array(ProvisioningScaffoldDeclarationSchema).optional(),
|
|
921
|
+
/**
|
|
922
|
+
* Biome-specific org-managed database declaration. When present the
|
|
923
|
+
* host provisions an org database, runs the declared migrations, and
|
|
924
|
+
* injects the org database identifiers into the runtime workload env.
|
|
925
|
+
*/
|
|
926
|
+
database: BiomeDatabaseDeclarationSchema.optional(),
|
|
927
|
+
/** Reserved for Phase 4 third-party signing. Validated only when present. */
|
|
928
|
+
signature: z
|
|
929
|
+
.object({
|
|
930
|
+
algorithm: z.string().min(1),
|
|
931
|
+
value: z.string().min(1),
|
|
932
|
+
keyId: z.string().min(1),
|
|
933
|
+
})
|
|
934
|
+
.strict()
|
|
935
|
+
.optional(),
|
|
936
|
+
// XSI Phase 1A additive fields. See `XsiManifestExtensionsShape`
|
|
937
|
+
// doc-comment above for the per-field meaning + plan citations.
|
|
938
|
+
...XsiManifestExtensionsShape,
|
|
939
|
+
})
|
|
940
|
+
.strict()
|
|
941
|
+
.superRefine((value, ctx) => {
|
|
942
|
+
validateServerPluginWebhookAndIntegrationRequirements(value, ctx);
|
|
943
|
+
validateServerBiomeAgentDeclarations(value, ctx);
|
|
944
|
+
validateServerPluginProvisioningDeclarations(value, ctx);
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
const WebBiomeXemaSchema = z
|
|
948
|
+
.object({
|
|
949
|
+
id: BiomeIdSchema,
|
|
950
|
+
displayName: z.string().min(1),
|
|
951
|
+
description: z.string().optional(),
|
|
952
|
+
display: BiomeDisplaySchema.optional(),
|
|
953
|
+
scope: ManifestBiomeScopeSchema,
|
|
954
|
+
target: z.literal('web'),
|
|
955
|
+
/**
|
|
956
|
+
* A platform/system web surface that renders in the org rail rather
|
|
957
|
+
* than the store. Defaults to `false`. A `systemSurface` web biome is a
|
|
958
|
+
* first-party operational surface (e.g. an admin/console panel) that the
|
|
959
|
+
* host shell mounts in the organization navigation rail; it is NOT a
|
|
960
|
+
* store-listed, org-installable biome. Resolve via `isSystemSurface` —
|
|
961
|
+
* never read raw.
|
|
962
|
+
*/
|
|
963
|
+
systemSurface: z.boolean().optional(),
|
|
964
|
+
/**
|
|
965
|
+
* Server biomes this web biome REQUIRES — its backend counterpart(s).
|
|
966
|
+
* Web biomes never depend on other web biomes — intentional, keeps the
|
|
967
|
+
* dependency graph two-tiered (web → server). Cross-target validation at
|
|
968
|
+
* boot ensures every referenced server id resolves in the catalog;
|
|
969
|
+
* otherwise biome-host refuses to start. Per-org, a web biome whose
|
|
970
|
+
* required server biomes are not all enabled resolves to ORPHANED.
|
|
971
|
+
*/
|
|
972
|
+
requiresServerBiomes: z.array(BiomeIdSchema).optional(),
|
|
973
|
+
/**
|
|
974
|
+
* Server biomes this web biome can OPTIONALLY integrate with (e.g. a
|
|
975
|
+
* GitHub/Jira connector). The web biome stays active without them; they
|
|
976
|
+
* are NOT part of the cross-target boot validation and never cause an
|
|
977
|
+
* ORPHANED state.
|
|
978
|
+
*/
|
|
979
|
+
optionalServerBiomes: z.array(BiomeIdSchema).optional(),
|
|
980
|
+
capabilities: WebBiomeCapabilitiesSchema.optional(),
|
|
981
|
+
signature: z
|
|
982
|
+
.object({
|
|
983
|
+
algorithm: z.string().min(1),
|
|
984
|
+
value: z.string().min(1),
|
|
985
|
+
keyId: z.string().min(1),
|
|
986
|
+
})
|
|
987
|
+
.strict()
|
|
988
|
+
.optional(),
|
|
989
|
+
// XSI Phase 1A additive fields — same shape on both targets so the
|
|
990
|
+
// biome-host can read them uniformly regardless of where the biome
|
|
991
|
+
// executes. See `XsiManifestExtensionsShape` above.
|
|
992
|
+
...XsiManifestExtensionsShape,
|
|
993
|
+
})
|
|
994
|
+
.strict();
|
|
995
|
+
|
|
996
|
+
export const BiomeXemaSchema = z.discriminatedUnion('target', [
|
|
997
|
+
ServerBiomeXemaSchema,
|
|
998
|
+
WebBiomeXemaSchema,
|
|
999
|
+
]);
|
|
1000
|
+
export type BiomeXema = z.infer<typeof BiomeXemaSchema>;
|
|
1001
|
+
export type ServerBiomeXema = z.infer<typeof ServerBiomeXemaSchema>;
|
|
1002
|
+
export type WebBiomeXema = z.infer<typeof WebBiomeXemaSchema>;
|
|
1003
|
+
|
|
1004
|
+
function validateServerPluginWebhookAndIntegrationRequirements(
|
|
1005
|
+
value: ServerBiomeXema,
|
|
1006
|
+
ctx: z.RefinementCtx,
|
|
1007
|
+
): void {
|
|
1008
|
+
if (value.webhookFilters && value.webhookFilters.length > 0) {
|
|
1009
|
+
const requirements = value.integrationRequirements ?? [];
|
|
1010
|
+
if (requirements.length === 0) {
|
|
1011
|
+
ctx.addIssue({
|
|
1012
|
+
code: 'custom',
|
|
1013
|
+
path: ['webhookFilters'],
|
|
1014
|
+
message:
|
|
1015
|
+
'webhookFilters is set but integrationRequirements is empty — a biome gating workflows on webhook events must declare which integration(s) it requires',
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (value.integrationRequirements && value.integrationRequirements.length > 0) {
|
|
1020
|
+
const shipsInstallSchema = value.ships?.content?.includes('biomeInstallSchema') ?? false;
|
|
1021
|
+
if (!shipsInstallSchema) {
|
|
1022
|
+
ctx.addIssue({
|
|
1023
|
+
code: 'custom',
|
|
1024
|
+
path: ['ships', 'content'],
|
|
1025
|
+
message:
|
|
1026
|
+
'biomes declaring integrationRequirements must also ship a `biomeInstallSchema` content kind so the install wizard can render a resource-selection form',
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function validateServerBiomeAgentDeclarations(
|
|
1033
|
+
value: ServerBiomeXema,
|
|
1034
|
+
ctx: z.RefinementCtx,
|
|
1035
|
+
): void {
|
|
1036
|
+
const shipsAgents = value.ships?.content?.includes('agents') ?? false;
|
|
1037
|
+
const hasAgents = (value.agents?.length ?? 0) > 0;
|
|
1038
|
+
if (shipsAgents && !hasAgents) {
|
|
1039
|
+
ctx.addIssue({
|
|
1040
|
+
code: 'custom',
|
|
1041
|
+
path: ['agents'],
|
|
1042
|
+
message:
|
|
1043
|
+
'ships.content includes "agents" — manifest must declare each shipped agent under `agents[]` (slug + mode). The boot-time cross-validator enforces parity with on-disk agents/*.md files.',
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
if (!shipsAgents && hasAgents) {
|
|
1047
|
+
ctx.addIssue({
|
|
1048
|
+
code: 'custom',
|
|
1049
|
+
path: ['agents'],
|
|
1050
|
+
message:
|
|
1051
|
+
'agents[] is declared but ships.content does not include "agents" — add "agents" to ships.content or remove the agents[] block',
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
if (hasAgents) {
|
|
1055
|
+
const slugs = new Set<string>();
|
|
1056
|
+
value.agents?.forEach((decl, idx) => {
|
|
1057
|
+
if (slugs.has(decl.slug)) {
|
|
1058
|
+
ctx.addIssue({
|
|
1059
|
+
code: 'custom',
|
|
1060
|
+
path: ['agents', idx, 'slug'],
|
|
1061
|
+
message: `duplicate agent slug "${decl.slug}" — each slug maps to a single agents/<slug>.md file`,
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
slugs.add(decl.slug);
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function validateServerPluginProvisioningDeclarations(
|
|
1070
|
+
value: ServerBiomeXema,
|
|
1071
|
+
ctx: z.RefinementCtx,
|
|
1072
|
+
): void {
|
|
1073
|
+
const shipsProvisioning = value.ships?.content?.includes('provisioningScaffolds') ?? false;
|
|
1074
|
+
const hasProvisioning = (value.provisioning?.length ?? 0) > 0;
|
|
1075
|
+
if (shipsProvisioning && !hasProvisioning) {
|
|
1076
|
+
ctx.addIssue({
|
|
1077
|
+
code: 'custom',
|
|
1078
|
+
path: ['provisioning'],
|
|
1079
|
+
message:
|
|
1080
|
+
'ships.content includes "provisioningScaffolds" — manifest must declare each scaffold under `provisioning[]`',
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
if (!shipsProvisioning && hasProvisioning) {
|
|
1084
|
+
ctx.addIssue({
|
|
1085
|
+
code: 'custom',
|
|
1086
|
+
path: ['provisioning'],
|
|
1087
|
+
message:
|
|
1088
|
+
'provisioning[] is declared but ships.content does not include "provisioningScaffolds" — add it or remove the provisioning[] block',
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const ManifestNameSchema = z
|
|
1094
|
+
.string()
|
|
1095
|
+
.min(1)
|
|
1096
|
+
.regex(/^@[^/]+\/[^/]+$/, 'biome package name must be scoped');
|
|
1097
|
+
|
|
1098
|
+
export const BiomeManifestSchema = z
|
|
1099
|
+
.object({
|
|
1100
|
+
name: ManifestNameSchema,
|
|
1101
|
+
version: z.string().min(1),
|
|
1102
|
+
xema: BiomeXemaSchema,
|
|
1103
|
+
})
|
|
1104
|
+
.passthrough(); // tolerate extra top-level npm fields
|
|
1105
|
+
|
|
1106
|
+
export type BiomeManifest = z.infer<typeof BiomeManifestSchema>;
|
|
1107
|
+
export type ServerBiomeManifest = BiomeManifest & { xema: ServerBiomeXema };
|
|
1108
|
+
export type WebBiomeManifest = BiomeManifest & { xema: WebBiomeXema };
|
|
1109
|
+
|
|
1110
|
+
export class BiomeManifestParseError extends Error {
|
|
1111
|
+
readonly issues: readonly z.core.$ZodIssue[];
|
|
1112
|
+
constructor(issues: readonly z.core.$ZodIssue[]) {
|
|
1113
|
+
super(
|
|
1114
|
+
`Invalid xema-biome.json: ${issues
|
|
1115
|
+
.map((i) => `[${i.path.join('.')}] ${i.message}`)
|
|
1116
|
+
.join('; ')}`,
|
|
1117
|
+
);
|
|
1118
|
+
this.name = 'BiomeManifestParseError';
|
|
1119
|
+
this.issues = issues;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
export function parseBiomeManifest(raw: unknown): BiomeManifest {
|
|
1124
|
+
const result = BiomeManifestSchema.safeParse(raw);
|
|
1125
|
+
if (!result.success) {
|
|
1126
|
+
throw new BiomeManifestParseError(result.error.issues);
|
|
1127
|
+
}
|
|
1128
|
+
return result.data;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
export function isServerBiomeManifest(
|
|
1132
|
+
manifest: BiomeManifest,
|
|
1133
|
+
): manifest is ServerBiomeManifest {
|
|
1134
|
+
return manifest.xema.target === 'server';
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
export function isWebBiomeManifest(
|
|
1138
|
+
manifest: BiomeManifest,
|
|
1139
|
+
): manifest is WebBiomeManifest {
|
|
1140
|
+
return manifest.xema.target === 'web';
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* Whether a web biome is a platform/system web surface that renders in the
|
|
1145
|
+
* org rail rather than the store. Applies the default (`false`) when the
|
|
1146
|
+
* field is omitted, and is always `false` for server biomes (the field is
|
|
1147
|
+
* web-only). Never read `xema.systemSurface` raw — go through this so the
|
|
1148
|
+
* default is applied in exactly one place.
|
|
1149
|
+
*/
|
|
1150
|
+
export function isSystemSurface(manifest: BiomeManifest): boolean {
|
|
1151
|
+
if (manifest.xema.target !== 'web') {
|
|
1152
|
+
return false;
|
|
1153
|
+
}
|
|
1154
|
+
return manifest.xema.systemSurface ?? false;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Effective audience of a biome, applying the default (`org`) when the
|
|
1159
|
+
* manifest omits the field. Never read `xema.audience` raw — go through this so
|
|
1160
|
+
* the default is applied in exactly one place.
|
|
1161
|
+
*/
|
|
1162
|
+
export function getBiomeAudience(manifest: BiomeManifest): BiomeAudience {
|
|
1163
|
+
return manifest.xema.audience ?? DEFAULT_BIOME_AUDIENCE;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Whether the biome should appear in the store catalog. An `operator`-audience
|
|
1168
|
+
* biome is NEVER store-listed (hidden from orgs by definition); otherwise the
|
|
1169
|
+
* manifest `storeListed` flag wins, defaulting to `true`.
|
|
1170
|
+
*/
|
|
1171
|
+
export function isBiomeStoreListed(manifest: BiomeManifest): boolean {
|
|
1172
|
+
if (getBiomeAudience(manifest) === BiomeAudience.Operator) {
|
|
1173
|
+
return false;
|
|
1174
|
+
}
|
|
1175
|
+
return manifest.xema.storeListed ?? true;
|
|
1176
|
+
}
|