@wibly/internal-manifest 0.1.1
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/CHANGELOG.md +6 -0
- package/package.json +25 -0
- package/src/content-rating.test.ts +26 -0
- package/src/content-rating.ts +51 -0
- package/src/fixtures.ts +229 -0
- package/src/index.ts +126 -0
- package/src/manifest.test.ts +351 -0
- package/src/manifest.ts +607 -0
- package/src/phase.test.ts +227 -0
- package/src/phase.ts +304 -0
- package/src/portal-display.test.ts +54 -0
- package/src/portal-display.ts +40 -0
- package/src/validate.test.ts +310 -0
- package/src/validate.ts +385 -0
package/CHANGELOG.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wibly/internal-manifest",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Wibly @wibly/internal-manifest",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"license": "UNLICENSED",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/wibly/wibly"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@wibly/internal-protocol": "0.1.1",
|
|
21
|
+
"@wibly/internal-shared": "0.1.1",
|
|
22
|
+
"zod": "^3.25.76"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {}
|
|
25
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
isSmutTierCapable,
|
|
5
|
+
portalContentRatingLabel,
|
|
6
|
+
safetyFloorFromContentRatingTier,
|
|
7
|
+
} from './content-rating.js';
|
|
8
|
+
|
|
9
|
+
describe('content-rating mappers', () => {
|
|
10
|
+
it('maps tiers to safety floors', () => {
|
|
11
|
+
expect(safetyFloorFromContentRatingTier('none')).toBe('general');
|
|
12
|
+
expect(safetyFloorFromContentRatingTier('pg13')).toBe('pg13');
|
|
13
|
+
expect(safetyFloorFromContentRatingTier('mature')).toBe('mature');
|
|
14
|
+
expect(safetyFloorFromContentRatingTier('extra_smut')).toBe('mature');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('enables smut tier only for extra_smut', () => {
|
|
18
|
+
expect(isSmutTierCapable('extra_smut')).toBe(true);
|
|
19
|
+
expect(isSmutTierCapable('mature')).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('labels portal badges', () => {
|
|
23
|
+
expect(portalContentRatingLabel('none')).toBe('All ages');
|
|
24
|
+
expect(portalContentRatingLabel('extra_smut')).toBe('Extra Smut');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ContentRatingFloor,
|
|
3
|
+
ExperienceContentRatingTier,
|
|
4
|
+
} from './manifest.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Map the unified Experience content-rating tier to the Safety
|
|
8
|
+
* pipeline floor (`general` | `pg13` | `mature`).
|
|
9
|
+
*/
|
|
10
|
+
export const safetyFloorFromContentRatingTier = (
|
|
11
|
+
tier: ExperienceContentRatingTier,
|
|
12
|
+
): ContentRatingFloor => {
|
|
13
|
+
switch (tier) {
|
|
14
|
+
case 'none':
|
|
15
|
+
return 'general';
|
|
16
|
+
case 'pg13':
|
|
17
|
+
return 'pg13';
|
|
18
|
+
case 'mature':
|
|
19
|
+
case 'extra_smut':
|
|
20
|
+
return 'mature';
|
|
21
|
+
default: {
|
|
22
|
+
const _exhaustive: never = tier;
|
|
23
|
+
return _exhaustive;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Whether the User Portal should render the smut-tier selector. */
|
|
29
|
+
export const isSmutTierCapable = (
|
|
30
|
+
tier: ExperienceContentRatingTier,
|
|
31
|
+
): boolean => tier === 'extra_smut';
|
|
32
|
+
|
|
33
|
+
/** Human-readable badge label for catalogue and detail pages. */
|
|
34
|
+
export const portalContentRatingLabel = (
|
|
35
|
+
tier: ExperienceContentRatingTier,
|
|
36
|
+
): string => {
|
|
37
|
+
switch (tier) {
|
|
38
|
+
case 'none':
|
|
39
|
+
return 'All ages';
|
|
40
|
+
case 'pg13':
|
|
41
|
+
return 'PG-13';
|
|
42
|
+
case 'mature':
|
|
43
|
+
return 'Mature';
|
|
44
|
+
case 'extra_smut':
|
|
45
|
+
return 'Extra Smut';
|
|
46
|
+
default: {
|
|
47
|
+
const _exhaustive: never = tier;
|
|
48
|
+
return _exhaustive;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
package/src/fixtures.ts
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical manifest fixtures used by the schema and validate-flow
|
|
3
|
+
* tests. The baseline `validManifestFixture` is a minimal-but-complete
|
|
4
|
+
* Experience that satisfies every required field and every structural
|
|
5
|
+
* check; per-failure-case fixtures derive from it via shallow clone +
|
|
6
|
+
* mutation in the test files (per the chunk-B1 acceptance: "one fixture
|
|
7
|
+
* per failure case so the AI doesn't conflate two errors when only one
|
|
8
|
+
* was triggered").
|
|
9
|
+
*
|
|
10
|
+
* Not imported by runtime code (services, the Admin app, the Player
|
|
11
|
+
* shell). The dev seed (`packages/db/src/seed-dev.ts`) is allowed —
|
|
12
|
+
* the seed is itself a fixture-loader, and using this shared baseline
|
|
13
|
+
* keeps the dev-seeded `experience_versions.manifest` blob aligned with
|
|
14
|
+
* the schema the Inference Gateway (chunk B2) loads at request time.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { Manifest } from './manifest.js';
|
|
18
|
+
import type { WorkflowSideEffect } from './phase.js';
|
|
19
|
+
|
|
20
|
+
/** Shallow patch for per-test / fixture mutations on a workflow phase. */
|
|
21
|
+
export type PhaseTestPatch = {
|
|
22
|
+
readonly id?: string;
|
|
23
|
+
readonly transitions?: ReadonlyArray<{ readonly to: string; readonly when?: string }>;
|
|
24
|
+
readonly sideEffects?: readonly WorkflowSideEffect[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Mutate one phase in a cloned manifest. Manifest phases are typed
|
|
29
|
+
* readonly; tests use this helper instead of unsafe casts.
|
|
30
|
+
*/
|
|
31
|
+
export const patchPhase = (
|
|
32
|
+
manifest: Manifest,
|
|
33
|
+
phaseId: string,
|
|
34
|
+
patch: PhaseTestPatch,
|
|
35
|
+
): void => {
|
|
36
|
+
const phase = manifest.workflow.phases.find((p) => p.id === phaseId);
|
|
37
|
+
if (!phase) return;
|
|
38
|
+
Object.assign(phase as object, patch);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Mutate a phase by index (same semantics as `patchPhase`). */
|
|
42
|
+
export const patchPhaseAt = (
|
|
43
|
+
manifest: Manifest,
|
|
44
|
+
index: number,
|
|
45
|
+
patch: PhaseTestPatch,
|
|
46
|
+
): void => {
|
|
47
|
+
const phase = manifest.workflow.phases[index];
|
|
48
|
+
if (!phase) return;
|
|
49
|
+
Object.assign(phase as object, patch);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const TENANT_ID = 'tnt_V1StGXR8_Z5jdHi6B_myT';
|
|
53
|
+
const EXPERIENCE_ID = 'exp_HelloWorldFixture000_';
|
|
54
|
+
const PERSONA_ID = 'per_CrumbHostPersona00_';
|
|
55
|
+
const FIXED_ISO = '2026-01-01T00:00:00.000Z';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A minimal Hello World shape: lobby → guess → resolved, with one
|
|
59
|
+
* persona binding, one quality tier, one award. Designed so a
|
|
60
|
+
* single-field mutation can trigger any one of the structural checks
|
|
61
|
+
* without colliding with another rule.
|
|
62
|
+
*/
|
|
63
|
+
export const validManifestFixture: Manifest = {
|
|
64
|
+
id: EXPERIENCE_ID,
|
|
65
|
+
version: '1.0.0',
|
|
66
|
+
name: 'Hello World',
|
|
67
|
+
description: 'The smallest manifest the validator accepts.',
|
|
68
|
+
tenant: TENANT_ID,
|
|
69
|
+
creator: 'wibly-platform',
|
|
70
|
+
createdAt: FIXED_ISO,
|
|
71
|
+
personaBindings: [
|
|
72
|
+
{ role: 'host', personaId: PERSONA_ID },
|
|
73
|
+
],
|
|
74
|
+
inferenceEnvelope: {
|
|
75
|
+
maxLlmCallsPerSession: 20,
|
|
76
|
+
maxTokensInPerCall: 2048,
|
|
77
|
+
maxTokensOutPerCall: 1024,
|
|
78
|
+
maxTtsSecondsPerSession: 120,
|
|
79
|
+
qualityTiers: ['fast', 'standard', 'premium', 'creative'],
|
|
80
|
+
},
|
|
81
|
+
stateSchema: {
|
|
82
|
+
session: { type: 'object', properties: { round: { type: 'number' } } },
|
|
83
|
+
host: { type: 'object', properties: {} },
|
|
84
|
+
playerPublic: { type: 'object', properties: { score: { type: 'number' } } },
|
|
85
|
+
playerPrivate: { type: 'object', properties: {} },
|
|
86
|
+
team: { type: 'object', properties: {} },
|
|
87
|
+
},
|
|
88
|
+
workflow: {
|
|
89
|
+
initialPhase: 'lobby',
|
|
90
|
+
phases: [
|
|
91
|
+
{
|
|
92
|
+
id: 'lobby',
|
|
93
|
+
inputSet: { actors: ['host'], inputType: 'start' },
|
|
94
|
+
collectionRule: { kind: 'manual' },
|
|
95
|
+
transitions: [{ to: 'guess' }],
|
|
96
|
+
sideEffects: [],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'guess',
|
|
100
|
+
inputSet: { actors: ['player'], inputType: 'guess' },
|
|
101
|
+
collectionRule: { kind: 'all_respond' },
|
|
102
|
+
transitions: [{ to: 'resolved' }],
|
|
103
|
+
sideEffects: [],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'resolved',
|
|
107
|
+
inputSet: { actors: ['host'], inputType: 'next' },
|
|
108
|
+
collectionRule: { kind: 'manual' },
|
|
109
|
+
transitions: [{ to: 'lobby', when: 'play_again' }],
|
|
110
|
+
sideEffects: [],
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
concurrentOpportunities: [
|
|
115
|
+
{
|
|
116
|
+
id: 'side_chat',
|
|
117
|
+
attachedToPhases: ['guess'],
|
|
118
|
+
inputSet: { actors: ['player'], inputType: 'chatter' },
|
|
119
|
+
collectionRule: { kind: 'timeout', ms: 30_000 },
|
|
120
|
+
scoringEffect: { kind: 'noop' },
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
scoring: {
|
|
124
|
+
dimensions: [
|
|
125
|
+
{ id: 'accuracy', label: 'Accuracy', weight: 1, scaleMin: 0, scaleMax: 10 },
|
|
126
|
+
],
|
|
127
|
+
aggregators: [{ kind: 'sum' }],
|
|
128
|
+
awards: [
|
|
129
|
+
{
|
|
130
|
+
id: 'top_guesser',
|
|
131
|
+
label: 'Top Guesser',
|
|
132
|
+
dimensionId: 'accuracy',
|
|
133
|
+
criterion: { kind: 'top_n', n: 1 },
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
lifecyclePolicies: [
|
|
138
|
+
{
|
|
139
|
+
situation: 'player_disconnect',
|
|
140
|
+
action: {
|
|
141
|
+
kind: 'pause_session',
|
|
142
|
+
timeoutMs: 30_000,
|
|
143
|
+
fallback: 'continue_without_them',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
promptSlots: {
|
|
148
|
+
experienceSystem: 'You are running Hello World, a tiny test Experience.',
|
|
149
|
+
callTypes: {
|
|
150
|
+
host_open_phase: 'Open the next phase with one short sentence.',
|
|
151
|
+
host_recap: {
|
|
152
|
+
template: 'Recap the round for {playerCount} players.',
|
|
153
|
+
vars: ['playerCount'],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
fallbackResponses: {
|
|
158
|
+
host_open_phase: 'Welcome — let us begin.',
|
|
159
|
+
host_recap: 'Thanks for playing.',
|
|
160
|
+
},
|
|
161
|
+
widgetDependencies: ['Lobby', 'GuessInput', 'Leaderboard'],
|
|
162
|
+
contentRating: {
|
|
163
|
+
tier: 'none',
|
|
164
|
+
audiences: ['consumer'],
|
|
165
|
+
},
|
|
166
|
+
portalMetadata: {
|
|
167
|
+
heroImageUrl: 'https://assets.wibly.example/fixtures/hello-world-hero.png',
|
|
168
|
+
gameplayImages: [],
|
|
169
|
+
sampleRoundDescription:
|
|
170
|
+
'A demonstration round where players guess a number and the host reveals the answer.',
|
|
171
|
+
occasionTags: ['party', 'quick_game'],
|
|
172
|
+
minPlayers: 3,
|
|
173
|
+
maxPlayers: 8,
|
|
174
|
+
estimatedDurationMinutes: 30,
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Deep clone of the fixture so per-test mutations don't bleed into
|
|
180
|
+
* other tests. `structuredClone` is in the Node 20 std lib (no polyfill
|
|
181
|
+
* needed) and handles the JSON-shaped state-schema slices correctly.
|
|
182
|
+
*
|
|
183
|
+
* Note: the cloned fixture's `workflow.phases[*].sideEffects` is
|
|
184
|
+
* `undefined` because `validManifestFixture` does not set the field
|
|
185
|
+
* — the `PhaseSchema.default([])` on `sideEffects` fills it in when
|
|
186
|
+
* the fixture is parsed via `ManifestSchema` / `validateManifest`. If
|
|
187
|
+
* a caller needs the parsed (post-default) shape, run the clone
|
|
188
|
+
* through `validateManifest` first.
|
|
189
|
+
*/
|
|
190
|
+
export const cloneFixture = (): Manifest =>
|
|
191
|
+
structuredClone(validManifestFixture);
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Chunk B8c — fixture variant that exercises the per-phase
|
|
195
|
+
* `sideEffects` declaration. Adds:
|
|
196
|
+
* - an `inference` side effect on `guess` that writes the structured
|
|
197
|
+
* output into `/session/inference/host_judge`,
|
|
198
|
+
* - a `scoring` side effect on `resolved` that credits a fixed +1 to
|
|
199
|
+
* the active player on the `accuracy` dimension,
|
|
200
|
+
*
|
|
201
|
+
* The integration tests under `services/runtime/src/__integration__/`
|
|
202
|
+
* consume this fixture; the unit tests stick with the bare
|
|
203
|
+
* `validManifestFixture` so the `sideEffects: []` default path stays
|
|
204
|
+
* exercised.
|
|
205
|
+
*/
|
|
206
|
+
export const cloneFixtureWithInferenceSideEffect = (): Manifest => {
|
|
207
|
+
const fixture = cloneFixture();
|
|
208
|
+
patchPhase(fixture, 'guess', {
|
|
209
|
+
sideEffects: [
|
|
210
|
+
{
|
|
211
|
+
kind: 'inference',
|
|
212
|
+
callKind: 'host_judge',
|
|
213
|
+
qualityTier: 'standard',
|
|
214
|
+
targetPath: '/session/inference/host_judge',
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
});
|
|
218
|
+
patchPhase(fixture, 'resolved', {
|
|
219
|
+
sideEffects: [
|
|
220
|
+
{
|
|
221
|
+
kind: 'scoring',
|
|
222
|
+
dimension: 'accuracy',
|
|
223
|
+
value: 1,
|
|
224
|
+
source: 'compute',
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
});
|
|
228
|
+
return fixture;
|
|
229
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@platform/manifest` — Experience-manifest schemas and validator.
|
|
3
|
+
*
|
|
4
|
+
* Per Vibecode Dev Plan §2.5 the manifest blob lives in a `JSONB`
|
|
5
|
+
* column on `experience_versions`; this package owns the Zod schema
|
|
6
|
+
* that gates writes to that column and the `validateManifest` function
|
|
7
|
+
* that runs on every read / publish boundary. Per the chunk-B1 trap,
|
|
8
|
+
* `validateManifest` is the *only* validation entrypoint downstream
|
|
9
|
+
* services should reach for — ad-hoc Zod parses or shape checks
|
|
10
|
+
* elsewhere are a regression.
|
|
11
|
+
*
|
|
12
|
+
* Built in chunk B1; see `docs/chunks/B1.md` for the close note.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
ActorKindSchema,
|
|
17
|
+
CollectionRuleSchema,
|
|
18
|
+
InferenceSideEffectSchema,
|
|
19
|
+
InputSetSchema,
|
|
20
|
+
PersonaMemorySideEffectSchema,
|
|
21
|
+
PhaseSchema,
|
|
22
|
+
ScoringSideEffectSchema,
|
|
23
|
+
StateWriteSideEffectSchema,
|
|
24
|
+
TransitionSchema,
|
|
25
|
+
WorkflowSideEffectSchema,
|
|
26
|
+
type ActorKind,
|
|
27
|
+
type CollectionRule,
|
|
28
|
+
type InputSet,
|
|
29
|
+
type ManifestPhase,
|
|
30
|
+
type Phase,
|
|
31
|
+
type Transition,
|
|
32
|
+
type WorkflowSideEffect,
|
|
33
|
+
} from './phase.js';
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
isSmutTierCapable,
|
|
37
|
+
portalContentRatingLabel,
|
|
38
|
+
safetyFloorFromContentRatingTier,
|
|
39
|
+
} from './content-rating.js';
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
DEFAULT_ESTIMATED_DURATION_MINUTES,
|
|
43
|
+
DEFAULT_MAX_PLAYERS,
|
|
44
|
+
DEFAULT_MIN_PLAYERS,
|
|
45
|
+
portalContentCategoryLabel,
|
|
46
|
+
portalDurationLabel,
|
|
47
|
+
portalPlayersLabel,
|
|
48
|
+
} from './portal-display.js';
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
AwardCriterionSchema,
|
|
52
|
+
AwardDefinitionSchema,
|
|
53
|
+
BundleContextSlotsSchema,
|
|
54
|
+
CallKindSchema,
|
|
55
|
+
ConcurrentOpportunitySchema,
|
|
56
|
+
ContentRatingAudienceSchema,
|
|
57
|
+
ContentRatingFloorSchema,
|
|
58
|
+
ContentRatingSchema,
|
|
59
|
+
ExperienceContentRatingTierSchema,
|
|
60
|
+
FallbackResponsesSchema,
|
|
61
|
+
GameplayImageSchema,
|
|
62
|
+
GameplayVideoSchema,
|
|
63
|
+
InferenceEnvelopeSchema,
|
|
64
|
+
LifecycleActionSchema,
|
|
65
|
+
LifecyclePolicySchema,
|
|
66
|
+
LifecycleSituationSchema,
|
|
67
|
+
ManifestIdSchema,
|
|
68
|
+
ManifestSchema,
|
|
69
|
+
ManifestTenantIdSchema,
|
|
70
|
+
OccasionTagSchema,
|
|
71
|
+
PersonaBindingSchema,
|
|
72
|
+
PersonaIdSchema,
|
|
73
|
+
PortalMetadataSchema,
|
|
74
|
+
PromptSlotsSchema,
|
|
75
|
+
PromptSlotValueSchema,
|
|
76
|
+
QualityTierSchema,
|
|
77
|
+
ScoringAggregatorSchema,
|
|
78
|
+
ScoringDimensionSchema,
|
|
79
|
+
ScoringSchema,
|
|
80
|
+
StateSchemaSchema,
|
|
81
|
+
WorkflowSchema,
|
|
82
|
+
type AwardCriterion,
|
|
83
|
+
type AwardDefinition,
|
|
84
|
+
type BundleContextSlots,
|
|
85
|
+
type CallKind,
|
|
86
|
+
type ConcurrentOpportunity,
|
|
87
|
+
type ContentRating,
|
|
88
|
+
type ContentRatingAudience,
|
|
89
|
+
type ContentRatingFloor,
|
|
90
|
+
type ExperienceContentRatingTier,
|
|
91
|
+
type FallbackResponses,
|
|
92
|
+
type GameplayImage,
|
|
93
|
+
type GameplayVideo,
|
|
94
|
+
type InferenceEnvelope,
|
|
95
|
+
type JsonValue,
|
|
96
|
+
type LifecycleAction,
|
|
97
|
+
type LifecyclePolicy,
|
|
98
|
+
type LifecycleSituation,
|
|
99
|
+
type Manifest,
|
|
100
|
+
type ManifestId,
|
|
101
|
+
type ManifestTenantId,
|
|
102
|
+
type OccasionTag,
|
|
103
|
+
type PersonaBinding,
|
|
104
|
+
type PersonaId,
|
|
105
|
+
type PortalMetadata,
|
|
106
|
+
type PromptSlots,
|
|
107
|
+
type PromptSlotValue,
|
|
108
|
+
type QualityTier,
|
|
109
|
+
type Scoring,
|
|
110
|
+
type ScoringAggregator,
|
|
111
|
+
type ScoringDimension,
|
|
112
|
+
type StateSchema,
|
|
113
|
+
type Workflow,
|
|
114
|
+
} from './manifest.js';
|
|
115
|
+
|
|
116
|
+
export {
|
|
117
|
+
validateManifest,
|
|
118
|
+
type ValidationError,
|
|
119
|
+
type ValidationErrorCode,
|
|
120
|
+
} from './validate.js';
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
cloneFixture,
|
|
124
|
+
cloneFixtureWithInferenceSideEffect,
|
|
125
|
+
validManifestFixture,
|
|
126
|
+
} from './fixtures.js';
|