@voce-engine/core 0.1.0-rc.2 → 0.1.0-rc.4
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/README.md +23 -2
- package/dist/composition.d.ts +36 -0
- package/dist/composition.js +108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +98 -1
- package/dist/m4.js +326 -47
- package/dist/m5.d.ts +2 -2
- package/dist/m5.js +104 -9
- package/dist/m6.d.ts +1 -0
- package/dist/m6.js +16 -4
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -2,8 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
Deterministic ScenarioPack resolution, constraint compilation, reference planning, Prompt IR, offline execution, and evaluation primitives.
|
|
4
4
|
|
|
5
|
-
> `0.1.0-rc.
|
|
5
|
+
> `0.1.0-rc.4` is a release candidate, not production-ready. APIs may change before `0.1.0`.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @voce-engine/core@0.1.0-rc.
|
|
8
|
+
npm install @voce-engine/core@0.1.0-rc.4
|
|
9
9
|
```
|
|
10
|
+
|
|
11
|
+
## Visual composition presets
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { compileConstraints, expandVisualCompositionPreset } from '@voce-engine/core'
|
|
15
|
+
|
|
16
|
+
const compositionChanges = expandVisualCompositionPreset('dutch-angle', {
|
|
17
|
+
inputs: { direction: 'right' },
|
|
18
|
+
sourceHintIds: ['ui-card:dutch-angle'],
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const result = compileConstraints({
|
|
22
|
+
...existingCompilationInput,
|
|
23
|
+
changeIntents: [
|
|
24
|
+
...existingCompilationInput.changeIntents,
|
|
25
|
+
...compositionChanges,
|
|
26
|
+
],
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Hosts should display the example artwork but submit only the stable preset ID and typed inputs. The artwork is not a reference asset and the selector does not consume reference budget. See the [complete preset and integration guide](../../docs/visual-composition.md) or its [简体中文 version](../../docs/zh-CN/visual-composition.md).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ChangeIntent, JsonValue, OntologyPathDefinition, Provenance } from '@voce-engine/contracts';
|
|
2
|
+
export declare const VISUAL_COMPOSITION_SCHEMA_VERSION: "voce.visual-composition/v1alpha1";
|
|
3
|
+
export declare const VISUAL_COMPOSITION_CATALOG_ID = "visual-composition.v1";
|
|
4
|
+
export declare const VISUAL_COMPOSITION_FIXED_TIME = "2026-01-01T00:00:00.000Z";
|
|
5
|
+
export interface CompositionChangeTemplate {
|
|
6
|
+
operation: ChangeIntent['operation'];
|
|
7
|
+
targetPath: string;
|
|
8
|
+
requestedValue?: JsonValue;
|
|
9
|
+
valueFrom?: 'direction' | 'surface' | 'silhouette';
|
|
10
|
+
}
|
|
11
|
+
export interface VisualCompositionPreset {
|
|
12
|
+
id: string;
|
|
13
|
+
labelKey: string;
|
|
14
|
+
descriptionKey: string;
|
|
15
|
+
category: 'framing' | 'view' | 'layout' | 'depth' | 'lens';
|
|
16
|
+
compatibilityHints?: string[];
|
|
17
|
+
changes: CompositionChangeTemplate[];
|
|
18
|
+
requiredInputs?: string[];
|
|
19
|
+
}
|
|
20
|
+
export interface VisualCompositionCatalog {
|
|
21
|
+
schemaVersion: typeof VISUAL_COMPOSITION_SCHEMA_VERSION;
|
|
22
|
+
id: string;
|
|
23
|
+
paths: OntologyPathDefinition[];
|
|
24
|
+
presets: VisualCompositionPreset[];
|
|
25
|
+
catalogHash: string;
|
|
26
|
+
}
|
|
27
|
+
export declare const VISUAL_COMPOSITION_PATHS: OntologyPathDefinition[];
|
|
28
|
+
export declare const VISUAL_COMPOSITION_PRESETS: VisualCompositionPreset[];
|
|
29
|
+
export declare const VISUAL_COMPOSITION_CATALOG: VisualCompositionCatalog;
|
|
30
|
+
export declare function computeVisualCompositionCatalogHash(catalog?: VisualCompositionCatalog): string;
|
|
31
|
+
export declare function expandVisualCompositionPreset(presetId: string, options?: {
|
|
32
|
+
inputs?: Record<string, JsonValue>;
|
|
33
|
+
provenance?: Provenance;
|
|
34
|
+
sourceHintIds?: string[];
|
|
35
|
+
}): ChangeIntent[];
|
|
36
|
+
export declare function compositionCatalogCanonicalJson(catalog?: VisualCompositionCatalog): string;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { canonicalize, sha256 } from './canonical.js';
|
|
2
|
+
export const VISUAL_COMPOSITION_SCHEMA_VERSION = 'voce.visual-composition/v1alpha1';
|
|
3
|
+
export const VISUAL_COMPOSITION_CATALOG_ID = 'visual-composition.v1';
|
|
4
|
+
export const VISUAL_COMPOSITION_FIXED_TIME = '2026-01-01T00:00:00.000Z';
|
|
5
|
+
const enumPath = (path, allowedValues, defaultImportance = 'preferred') => ({ path, valueKind: 'enum', cardinality: 'one', allowedValues, defaultImportance });
|
|
6
|
+
const boolPath = (path, defaultImportance = 'preferred') => ({ path, valueKind: 'boolean', cardinality: 'one', defaultImportance });
|
|
7
|
+
const clone = (value) => JSON.parse(JSON.stringify(value));
|
|
8
|
+
function deepFreeze(value) {
|
|
9
|
+
if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
10
|
+
for (const child of Object.values(value))
|
|
11
|
+
deepFreeze(child);
|
|
12
|
+
Object.freeze(value);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
export const VISUAL_COMPOSITION_PATHS = [
|
|
17
|
+
enumPath('camera.framing.shotScale', ['extreme_close_up', 'close_up', 'head_and_shoulders', 'bust_shot', 'medium_close_up', 'medium_shot', 'knee_shot', 'full_shot', 'long_shot', 'extreme_long_shot']),
|
|
18
|
+
boolPath('camera.framing.crop.keepHead'), boolPath('camera.framing.crop.keepHands'), boolPath('camera.framing.crop.keepBothFeet'), boolPath('camera.framing.crop.keepProduct'), boolPath('camera.framing.crop.keepSignatureProp'),
|
|
19
|
+
enumPath('camera.view.elevation', ['eye_level', 'low_angle', 'high_angle', 'birds_eye']), enumPath('camera.view.relationship', ['front', 'three_quarter', 'profile', 'rear', 'over_the_shoulder']),
|
|
20
|
+
enumPath('camera.roll.mode', ['level', 'dutch_left', 'dutch_right']), enumPath('camera.lens.focalLengthClass', ['ultra_wide', 'wide', 'normal', 'telephoto', 'super_telephoto']), enumPath('camera.lens.perspective', ['expanded', 'natural', 'compressed']),
|
|
21
|
+
boolPath('camera.composition.patterns.centeredSymmetry'), boolPath('camera.composition.patterns.ruleOfThirds'), boolPath('camera.composition.patterns.leadingLines'), boolPath('camera.composition.patterns.diagonal'), boolPath('camera.composition.patterns.sCurve'), boolPath('camera.composition.patterns.triangle'),
|
|
22
|
+
enumPath('camera.composition.placement', ['center', 'left_third', 'right_third', 'upper_third', 'lower_third']), enumPath('camera.composition.negativeSpace', ['none', 'left', 'right', 'above', 'below', 'surrounding']),
|
|
23
|
+
boolPath('camera.composition.leadingRoom.enabled'), enumPath('camera.composition.leadingRoom.direction', ['left', 'right', 'forward', 'up', 'down']),
|
|
24
|
+
enumPath('camera.composition.foregroundTreatment', ['clear', 'soft_obstruction', 'strong_obstruction']), boolPath('camera.composition.framingDevices.frameWithinFrame'), boolPath('camera.composition.framingDevices.environmentalPortrait'),
|
|
25
|
+
boolPath('camera.composition.reflection.enabled'), enumPath('camera.composition.reflection.surface', ['mirror', 'glass', 'water', 'screen', 'polished_surface']), enumPath('camera.composition.reflection.role', ['supporting', 'co_primary', 'primary']), enumPath('camera.composition.environmentRelationship', ['isolated', 'contextual', 'environment_dominant']),
|
|
26
|
+
enumPath('lighting.subjectRendering', ['natural', 'silhouette']),
|
|
27
|
+
];
|
|
28
|
+
const preset = (id, category, changes, requiredInputs = [], compatibilityHints = []) => ({ id, labelKey: `visualComposition.${id}.label`, descriptionKey: `visualComposition.${id}.description`, category, ...(compatibilityHints.length ? { compatibilityHints } : {}), changes, ...(requiredInputs.length ? { requiredInputs } : {}) });
|
|
29
|
+
export const VISUAL_COMPOSITION_PRESETS = [
|
|
30
|
+
preset('extreme-close-up', 'framing', [{ operation: 'replace', targetPath: 'camera.framing.shotScale', requestedValue: 'extreme_close_up' }]),
|
|
31
|
+
preset('close-up', 'framing', [{ operation: 'replace', targetPath: 'camera.framing.shotScale', requestedValue: 'close_up' }]),
|
|
32
|
+
preset('head-and-shoulders', 'framing', [{ operation: 'replace', targetPath: 'camera.framing.shotScale', requestedValue: 'head_and_shoulders' }, { operation: 'adjust', targetPath: 'camera.framing.crop.keepHead', requestedValue: true }]),
|
|
33
|
+
preset('bust-shot', 'framing', [{ operation: 'replace', targetPath: 'camera.framing.shotScale', requestedValue: 'bust_shot' }, { operation: 'adjust', targetPath: 'camera.framing.crop.keepHead', requestedValue: true }]),
|
|
34
|
+
preset('medium-close-up', 'framing', [{ operation: 'replace', targetPath: 'camera.framing.shotScale', requestedValue: 'medium_close_up' }]),
|
|
35
|
+
preset('medium-shot', 'framing', [{ operation: 'replace', targetPath: 'camera.framing.shotScale', requestedValue: 'medium_shot' }]),
|
|
36
|
+
preset('knee-shot', 'framing', [{ operation: 'replace', targetPath: 'camera.framing.shotScale', requestedValue: 'knee_shot' }]),
|
|
37
|
+
preset('full-shot', 'framing', [{ operation: 'replace', targetPath: 'camera.framing.shotScale', requestedValue: 'full_shot' }, { operation: 'adjust', targetPath: 'camera.framing.crop.keepBothFeet', requestedValue: true }]),
|
|
38
|
+
preset('long-shot', 'framing', [{ operation: 'replace', targetPath: 'camera.framing.shotScale', requestedValue: 'long_shot' }, { operation: 'adjust', targetPath: 'camera.composition.environmentRelationship', requestedValue: 'contextual' }]),
|
|
39
|
+
preset('extreme-long-shot', 'framing', [{ operation: 'replace', targetPath: 'camera.framing.shotScale', requestedValue: 'extreme_long_shot' }, { operation: 'adjust', targetPath: 'camera.composition.environmentRelationship', requestedValue: 'environment_dominant' }]),
|
|
40
|
+
preset('low-angle', 'view', [{ operation: 'adjust', targetPath: 'camera.view.elevation', requestedValue: 'low_angle' }]),
|
|
41
|
+
preset('high-angle', 'view', [{ operation: 'adjust', targetPath: 'camera.view.elevation', requestedValue: 'high_angle' }]),
|
|
42
|
+
preset('birds-eye-view', 'view', [{ operation: 'adjust', targetPath: 'camera.view.elevation', requestedValue: 'birds_eye' }]),
|
|
43
|
+
preset('over-the-shoulder', 'view', [{ operation: 'adjust', targetPath: 'camera.view.relationship', requestedValue: 'over_the_shoulder' }], [], ['foreground-shoulder-subject-or-declared-equivalent']),
|
|
44
|
+
preset('dutch-angle', 'view', [{ operation: 'adjust', targetPath: 'camera.roll.mode', valueFrom: 'direction' }], ['direction']),
|
|
45
|
+
preset('centered-symmetry', 'layout', [{ operation: 'adjust', targetPath: 'camera.composition.patterns.centeredSymmetry', requestedValue: true }, { operation: 'adjust', targetPath: 'camera.composition.placement', requestedValue: 'center' }]),
|
|
46
|
+
preset('rule-of-thirds', 'layout', [{ operation: 'adjust', targetPath: 'camera.composition.patterns.ruleOfThirds', requestedValue: true }]),
|
|
47
|
+
preset('leading-lines', 'layout', [{ operation: 'adjust', targetPath: 'camera.composition.patterns.leadingLines', requestedValue: true }], [], ['suitable-scene-geometry-or-review']),
|
|
48
|
+
preset('leading-room', 'layout', [{ operation: 'adjust', targetPath: 'camera.composition.leadingRoom.enabled', requestedValue: true }, { operation: 'adjust', targetPath: 'camera.composition.leadingRoom.direction', valueFrom: 'direction' }], ['direction']),
|
|
49
|
+
preset('diagonal-composition', 'layout', [{ operation: 'adjust', targetPath: 'camera.composition.patterns.diagonal', requestedValue: true }]),
|
|
50
|
+
preset('s-curve-composition', 'layout', [{ operation: 'adjust', targetPath: 'camera.composition.patterns.sCurve', requestedValue: true }]),
|
|
51
|
+
preset('triangle-composition', 'layout', [{ operation: 'adjust', targetPath: 'camera.composition.patterns.triangle', requestedValue: true }]),
|
|
52
|
+
preset('negative-space', 'layout', [{ operation: 'adjust', targetPath: 'camera.composition.negativeSpace', valueFrom: 'direction' }], ['direction']),
|
|
53
|
+
preset('frame-within-frame', 'depth', [{ operation: 'adjust', targetPath: 'camera.composition.framingDevices.frameWithinFrame', requestedValue: true }]),
|
|
54
|
+
preset('foreground-obstruction', 'depth', [{ operation: 'adjust', targetPath: 'camera.composition.foregroundTreatment', requestedValue: 'soft_obstruction' }]),
|
|
55
|
+
preset('profile-silhouette', 'view', [{ operation: 'adjust', targetPath: 'camera.view.relationship', requestedValue: 'profile' }]),
|
|
56
|
+
preset('reflection-composition', 'depth', [{ operation: 'adjust', targetPath: 'camera.composition.reflection.enabled', requestedValue: true }, { operation: 'adjust', targetPath: 'camera.composition.reflection.surface', valueFrom: 'surface' }], ['surface']),
|
|
57
|
+
preset('mirror-composition', 'depth', [{ operation: 'adjust', targetPath: 'camera.composition.reflection.enabled', requestedValue: true }, { operation: 'adjust', targetPath: 'camera.composition.reflection.surface', requestedValue: 'mirror' }]),
|
|
58
|
+
preset('telephoto-compression', 'lens', [{ operation: 'adjust', targetPath: 'camera.lens.focalLengthClass', requestedValue: 'telephoto' }, { operation: 'adjust', targetPath: 'camera.lens.perspective', requestedValue: 'compressed' }]),
|
|
59
|
+
preset('environmental-portrait', 'depth', [{ operation: 'adjust', targetPath: 'camera.composition.framingDevices.environmentalPortrait', requestedValue: true }, { operation: 'adjust', targetPath: 'camera.composition.environmentRelationship', requestedValue: 'contextual' }]),
|
|
60
|
+
];
|
|
61
|
+
deepFreeze(VISUAL_COMPOSITION_PATHS);
|
|
62
|
+
deepFreeze(VISUAL_COMPOSITION_PRESETS);
|
|
63
|
+
const VISUAL_COMPOSITION_CATALOG_BODY = { schemaVersion: VISUAL_COMPOSITION_SCHEMA_VERSION, id: VISUAL_COMPOSITION_CATALOG_ID, paths: VISUAL_COMPOSITION_PATHS, presets: VISUAL_COMPOSITION_PRESETS };
|
|
64
|
+
export const VISUAL_COMPOSITION_CATALOG = deepFreeze({ ...VISUAL_COMPOSITION_CATALOG_BODY, catalogHash: sha256(VISUAL_COMPOSITION_CATALOG_BODY) });
|
|
65
|
+
export function computeVisualCompositionCatalogHash(catalog = VISUAL_COMPOSITION_CATALOG) {
|
|
66
|
+
return sha256({ schemaVersion: catalog.schemaVersion, id: catalog.id, paths: catalog.paths, presets: catalog.presets });
|
|
67
|
+
}
|
|
68
|
+
function valueForTemplate(template, options) {
|
|
69
|
+
let value = template.valueFrom === undefined ? template.requestedValue : options[template.valueFrom];
|
|
70
|
+
if (template.valueFrom !== undefined && value === undefined)
|
|
71
|
+
throw new Error('COMPOSITION_PRESET_INPUT_REQUIRED');
|
|
72
|
+
if (template.targetPath === 'camera.roll.mode') {
|
|
73
|
+
if (value !== 'left' && value !== 'right')
|
|
74
|
+
throw new Error('COMPOSITION_DIRECTION_INVALID');
|
|
75
|
+
value = value === 'left' ? 'dutch_left' : 'dutch_right';
|
|
76
|
+
}
|
|
77
|
+
if (value === undefined)
|
|
78
|
+
return undefined;
|
|
79
|
+
const definition = VISUAL_COMPOSITION_PATHS.find((item) => item.path === template.targetPath);
|
|
80
|
+
const valid = definition?.valueKind === 'boolean' ? typeof value === 'boolean'
|
|
81
|
+
: definition?.valueKind === 'string' ? typeof value === 'string'
|
|
82
|
+
: definition?.valueKind === 'number' ? typeof value === 'number' && Number.isFinite(value)
|
|
83
|
+
: definition?.valueKind === 'enum' && definition.allowedValues?.some((allowed) => canonicalize(allowed) === canonicalize(value));
|
|
84
|
+
if (!definition || !valid)
|
|
85
|
+
throw new Error('COMPOSITION_PRESET_INPUT_INVALID');
|
|
86
|
+
return clone(value);
|
|
87
|
+
}
|
|
88
|
+
export function expandVisualCompositionPreset(presetId, options = {}) {
|
|
89
|
+
const selected = VISUAL_COMPOSITION_PRESETS.find((item) => item.id === presetId);
|
|
90
|
+
if (!selected)
|
|
91
|
+
throw new Error('COMPOSITION_PRESET_NOT_FOUND');
|
|
92
|
+
const inputs = options.inputs ?? {};
|
|
93
|
+
for (const key of selected.requiredInputs ?? [])
|
|
94
|
+
if (inputs[key] === undefined)
|
|
95
|
+
throw new Error('COMPOSITION_PRESET_INPUT_REQUIRED');
|
|
96
|
+
const provenance = options.provenance ?? { source: 'user_explicit', sourceIds: [presetId], createdBy: 'visual-composition-preset', createdAt: VISUAL_COMPOSITION_FIXED_TIME };
|
|
97
|
+
const sourceHintIds = [...new Set([presetId, ...(options.sourceHintIds ?? [])])].sort();
|
|
98
|
+
const changes = selected.changes.map((template, index) => {
|
|
99
|
+
const requestedValue = valueForTemplate(template, inputs);
|
|
100
|
+
return { schemaVersion: 'voce.change-intent/v1alpha1', id: `composition.${presetId}.${index + 1}`, operation: template.operation, targetPath: template.targetPath, ...(requestedValue === undefined ? {} : { requestedValue }), sourceHintIds: [...sourceHintIds], importance: 'preferred', provenance: clone(provenance) };
|
|
101
|
+
});
|
|
102
|
+
if (presetId === 'profile-silhouette' && inputs.silhouette === true)
|
|
103
|
+
changes.push({ schemaVersion: 'voce.change-intent/v1alpha1', id: `composition.${presetId}.silhouette`, operation: 'adjust', targetPath: 'lighting.subjectRendering', requestedValue: 'silhouette', sourceHintIds: [...sourceHintIds], importance: 'preferred', provenance: clone(provenance) });
|
|
104
|
+
return changes;
|
|
105
|
+
}
|
|
106
|
+
export function compositionCatalogCanonicalJson(catalog = VISUAL_COMPOSITION_CATALOG) {
|
|
107
|
+
return canonicalize({ schemaVersion: catalog.schemaVersion, id: catalog.id, paths: catalog.paths, presets: catalog.presets });
|
|
108
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -164,6 +164,74 @@ function contributionDigest(value) {
|
|
|
164
164
|
delete copy.contentDigest;
|
|
165
165
|
return sha256(copy);
|
|
166
166
|
}
|
|
167
|
+
function validateTypedContribution(category, value) {
|
|
168
|
+
if (category === 'ontologyVocabulary') {
|
|
169
|
+
if (!Array.isArray(value.paths))
|
|
170
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
171
|
+
const ids = new Set();
|
|
172
|
+
for (const raw of value.paths) {
|
|
173
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
174
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
175
|
+
const path = raw;
|
|
176
|
+
if (typeof path.path !== 'string' || !path.path || (path.valueKind !== 'boolean' && path.valueKind !== 'enum' && path.valueKind !== 'string' && path.valueKind !== 'number') || (path.cardinality !== 'one' && path.cardinality !== 'many') || ids.has(path.path))
|
|
177
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
178
|
+
if (path.valueKind === 'enum' && (!Array.isArray(path.allowedValues) || path.allowedValues.length === 0))
|
|
179
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
180
|
+
ids.add(path.path);
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (category === 'rulePacks') {
|
|
185
|
+
if (!Array.isArray(value.rules))
|
|
186
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
187
|
+
const ids = new Set();
|
|
188
|
+
for (const raw of value.rules) {
|
|
189
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
190
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
191
|
+
const rule = raw;
|
|
192
|
+
if (typeof rule.id !== 'string' || !rule.id || ids.has(rule.id) || !['incompatibility', 'dependency', 'cardinality', 'occlusion', 'resource'].includes(String(rule.kind)) || !Array.isArray(rule.operands) || rule.operands.length < 2 || !rule.resolution || typeof rule.resolution !== 'object' || Array.isArray(rule.resolution) || typeof rule.explanation !== 'string')
|
|
193
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
194
|
+
const operandIds = new Set();
|
|
195
|
+
for (const rawOperand of rule.operands) {
|
|
196
|
+
if (!rawOperand || typeof rawOperand !== 'object' || Array.isArray(rawOperand))
|
|
197
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
198
|
+
const operand = rawOperand;
|
|
199
|
+
if (typeof operand.id !== 'string' || !operand.id || operandIds.has(operand.id) || !Array.isArray(operand.conditions) || operand.conditions.length === 0)
|
|
200
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
201
|
+
operandIds.add(operand.id);
|
|
202
|
+
for (const rawCondition of operand.conditions) {
|
|
203
|
+
if (!rawCondition || typeof rawCondition !== 'object' || Array.isArray(rawCondition))
|
|
204
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
205
|
+
const condition = rawCondition;
|
|
206
|
+
if (typeof condition.path !== 'string' || !condition.path || !['present', 'absent', 'equals', 'contains'].includes(String(condition.operator)))
|
|
207
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
208
|
+
if ((condition.operator === 'present' || condition.operator === 'absent') && condition.value !== undefined)
|
|
209
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
210
|
+
if ((condition.operator === 'equals' || condition.operator === 'contains') && condition.value === undefined)
|
|
211
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const resolution = rule.resolution;
|
|
215
|
+
if ((resolution.strategy !== 'block' && resolution.strategy !== 'degrade_operand') || typeof resolution.reasonCode !== 'string' || !resolution.reasonCode || (resolution.strategy === 'degrade_operand' && (typeof resolution.operandId !== 'string' || !operandIds.has(resolution.operandId))))
|
|
216
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
217
|
+
ids.add(rule.id);
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (category === 'promptSections') {
|
|
222
|
+
if (!Array.isArray(value.sections))
|
|
223
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
224
|
+
const ids = new Set();
|
|
225
|
+
for (const raw of value.sections) {
|
|
226
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
227
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
228
|
+
const section = raw;
|
|
229
|
+
if (typeof section.id !== 'string' || !section.id || ids.has(section.id) || typeof section.group !== 'string' || !Number.isInteger(section.order) || !Array.isArray(section.pathPrefixes) || section.pathPrefixes.length === 0 || section.pathPrefixes.some((item) => typeof item !== 'string' || !item) || typeof section.templateKey !== 'string' || !section.templateKey)
|
|
230
|
+
throw new Error('PACK_TYPED_CONTRIBUTION_INVALID');
|
|
231
|
+
ids.add(section.id);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
167
235
|
function descriptorFor(definition, files) {
|
|
168
236
|
if (!definition || typeof definition !== 'object' || Array.isArray(definition) || !definition.manifest)
|
|
169
237
|
throw new Error('PACK_MANIFEST_INVALID');
|
|
@@ -197,6 +265,8 @@ function descriptorFor(definition, files) {
|
|
|
197
265
|
bodyIds.add(id);
|
|
198
266
|
if (indexed.get(id) !== contribution.contentDigest || contributionDigest(contribution) !== contribution.contentDigest)
|
|
199
267
|
throw new Error('PACK_DIGEST_MISMATCH');
|
|
268
|
+
if ((category === 'ontologyVocabulary' && Array.isArray(contribution.paths)) || (category === 'rulePacks' && Array.isArray(contribution.rules)) || (category === 'promptSections' && Array.isArray(contribution.sections)))
|
|
269
|
+
validateTypedContribution(category, contribution);
|
|
200
270
|
}
|
|
201
271
|
}
|
|
202
272
|
const manifestHash = sha256(json(definition.manifest));
|
|
@@ -335,6 +405,29 @@ function composeCategory(category, ordered, packages, disabled, defaultValues) {
|
|
|
335
405
|
}
|
|
336
406
|
return { values: [...byId.values()].map((item) => withSource(item.value, item.packId, category, item.sourcePackIds)) };
|
|
337
407
|
}
|
|
408
|
+
function ontologyPathDefinitionCollision(contributions) {
|
|
409
|
+
const byPath = new Map();
|
|
410
|
+
for (const contribution of contributions) {
|
|
411
|
+
const paths = Array.isArray(contribution.paths) ? contribution.paths : [];
|
|
412
|
+
for (const raw of paths) {
|
|
413
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
414
|
+
continue;
|
|
415
|
+
const definition = raw;
|
|
416
|
+
if (typeof definition.path !== 'string')
|
|
417
|
+
continue;
|
|
418
|
+
const entries = byPath.get(definition.path) ?? [];
|
|
419
|
+
entries.push({ canonical: canonicalize(raw), packId: contribution.packId, contributionId: contribution.contributionId });
|
|
420
|
+
byPath.set(definition.path, entries);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
for (const path of [...byPath.keys()].sort(compareCodeUnits)) {
|
|
424
|
+
const entries = byPath.get(path);
|
|
425
|
+
if (new Set(entries.map((entry) => entry.canonical)).size <= 1)
|
|
426
|
+
continue;
|
|
427
|
+
return conflict('PACK_RULE_CONFLICT', [...new Set(entries.map((entry) => entry.packId))].sort(compareCodeUnits), `Ontology path ${path} has conflicting definitions after ScenarioPack composition.`, 'Make duplicate path definitions canonically identical or use distinct ontology paths.', [...new Set(entries.map((entry) => entry.contributionId))].sort(compareCodeUnits));
|
|
428
|
+
}
|
|
429
|
+
return undefined;
|
|
430
|
+
}
|
|
338
431
|
function overridePayload(override) {
|
|
339
432
|
return { id: override.id, operation: override.operation, reasonCode: override.reasonCode };
|
|
340
433
|
}
|
|
@@ -550,6 +643,9 @@ export function resolveScenario(selection, catalog, packages, descriptors = new
|
|
|
550
643
|
return { status: 'blocked', report: failureReport(selected, dependencyTrace, compositionTrace, overrideTraces, [composed.collision], warnings) };
|
|
551
644
|
effectiveValues[category] = composed.values;
|
|
552
645
|
}
|
|
646
|
+
const pathCollision = ontologyPathDefinitionCollision(effectiveValues.ontologyVocabulary);
|
|
647
|
+
if (pathCollision)
|
|
648
|
+
return { status: 'blocked', report: failureReport(selected, dependencyTrace, compositionTrace, overrideTraces, [pathCollision], warnings) };
|
|
553
649
|
const entries = ordered.map((entry) => ({
|
|
554
650
|
packId: entry.manifest.packId,
|
|
555
651
|
version: entry.manifest.version,
|
|
@@ -563,7 +659,7 @@ export function resolveScenario(selection, catalog, packages, descriptors = new
|
|
|
563
659
|
const lockBase = { schemaVersion: 'voce.scenario-pack-lock/v1alpha1', contractVersion: CONTRACT_VERSION, resolverVersion: catalog.resolverVersion, catalogHash: catalog.catalogHash, canonicalization: 'voce.canonical-json/v1alpha1', rootPackId: root.manifest.packId, entries, compositionOrder: order, ...(overlay ? { hostPolicyOverlayHash: overlay.overlayHash } : {}), hostOverrideHashes: appliedOverrides.map((item) => item.contentHash) };
|
|
564
660
|
const lock = { ...lockBase, compositionHash: sha256(json(lockBase)), lockHash: '' };
|
|
565
661
|
lock.lockHash = hashWithoutSelf(lock, 'lockHash');
|
|
566
|
-
const effective = { lockHash: lock.lockHash, rootPackId: root.manifest.packId, extensionPackIds: ordered.filter((entry) => entry.manifest.kind === 'extension').map((entry) => entry.manifest.packId), compositionOrder: order, configurations: Object.fromEntries([...configs].sort((left, right) => compareCodeUnits(left[0], right[0]))),
|
|
662
|
+
const effective = { lockHash: lock.lockHash, rootPackId: root.manifest.packId, extensionPackIds: ordered.filter((entry) => entry.manifest.kind === 'extension').map((entry) => entry.manifest.packId), compositionOrder: order, configurations: Object.fromEntries([...configs].sort((left, right) => compareCodeUnits(left[0], right[0]))), ontologyVocabulary: effectiveValues.ontologyVocabulary, rulePacks: effectiveValues.rulePacks, interpretationScopes: effectiveValues.interpretationScopes, promptSections: effectiveValues.promptSections, reviewTemplates: effectiveValues.reviewTemplates, defaults: effectiveValues.defaults, capabilityRequirements: ordered.flatMap((entry) => entry.manifest.capabilityRequirements), declarations: ordered.map((entry) => entry.manifest.declarations), appliedOverrides, effectiveScenarioHash: '' };
|
|
567
663
|
effective.effectiveScenarioHash = hashWithoutSelf(effective, 'effectiveScenarioHash');
|
|
568
664
|
const reportBase = { status: 'resolved', lockHash: lock.lockHash, effectiveScenarioHash: effective.effectiveScenarioHash, selected: selected.map((entry) => ({ packId: entry.manifest.packId, version: entry.manifest.version, kind: entry.manifest.kind, packageDigest: entry.packageDigest, manifestHash: entry.manifestHash })), dependencyTrace, compositionTrace, overrideTraces, conflicts: [], warnings };
|
|
569
665
|
return { status: 'resolved', lock, effectiveScenario: effective, report: { ...reportBase, reportHash: hashWithoutSelf(reportBase, 'reportHash') } };
|
|
@@ -606,3 +702,4 @@ export * from './evidence.js';
|
|
|
606
702
|
export * from './m4.js';
|
|
607
703
|
export * from './m5.js';
|
|
608
704
|
export * from './m6.js';
|
|
705
|
+
export * from './composition.js';
|
package/dist/m4.js
CHANGED
|
@@ -450,27 +450,68 @@ function internalRule(value, contributionId) {
|
|
|
450
450
|
const id = typeof object.id === 'string' ? object.id : typeof object.ruleId === 'string' ? object.ruleId : undefined;
|
|
451
451
|
if (!id)
|
|
452
452
|
return undefined;
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
const leftPaths = Array.isArray(object.leftPaths) ? object.leftPaths.filter((item) => typeof item === 'string') : Array.isArray(object.whenPaths) ? object.whenPaths.filter((item) => typeof item === 'string') : [];
|
|
456
|
-
const rightPaths = Array.isArray(object.rightPaths) ? object.rightPaths.filter((item) => typeof item === 'string') : Array.isArray(object.conflictingPaths) ? object.conflictingPaths.filter((item) => typeof item === 'string') : [];
|
|
457
|
-
if (leftPaths.length === 0 || rightPaths.length === 0)
|
|
453
|
+
const kind = object.kind;
|
|
454
|
+
if (kind !== 'incompatibility' && kind !== 'dependency' && kind !== 'cardinality' && kind !== 'occlusion' && kind !== 'resource')
|
|
458
455
|
return undefined;
|
|
459
|
-
|
|
460
|
-
|
|
456
|
+
if (!Array.isArray(object.operands) || object.operands.length < 2)
|
|
457
|
+
return undefined;
|
|
458
|
+
const conditionOperator = (value) => value === 'present' || value === 'absent' || value === 'equals' || value === 'contains';
|
|
459
|
+
const conditions = (value) => {
|
|
460
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
461
|
+
return undefined;
|
|
462
|
+
const result = [];
|
|
463
|
+
for (const candidate of value) {
|
|
464
|
+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate))
|
|
465
|
+
return undefined;
|
|
466
|
+
const condition = candidate;
|
|
467
|
+
if (typeof condition.path !== 'string' || !condition.path || !conditionOperator(condition.operator))
|
|
468
|
+
return undefined;
|
|
469
|
+
if ((condition.operator === 'present' || condition.operator === 'absent') && condition.value !== undefined)
|
|
470
|
+
return undefined;
|
|
471
|
+
if ((condition.operator === 'equals' || condition.operator === 'contains') && condition.value === undefined)
|
|
472
|
+
return undefined;
|
|
473
|
+
result.push({ path: condition.path, operator: condition.operator, ...(condition.value === undefined ? {} : { value: jsonReady(condition.value) }) });
|
|
474
|
+
}
|
|
475
|
+
return result;
|
|
476
|
+
};
|
|
477
|
+
const operands = [];
|
|
478
|
+
const operandIds = new Set();
|
|
479
|
+
for (const candidate of object.operands) {
|
|
480
|
+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate))
|
|
481
|
+
return undefined;
|
|
482
|
+
const operand = candidate;
|
|
483
|
+
if (typeof operand.id !== 'string' || !operand.id || operandIds.has(operand.id))
|
|
484
|
+
return undefined;
|
|
485
|
+
const operandConditions = conditions(operand.conditions);
|
|
486
|
+
if (!operandConditions)
|
|
487
|
+
return undefined;
|
|
488
|
+
operandIds.add(operand.id);
|
|
489
|
+
operands.push({ id: operand.id, conditions: operandConditions, match: 'all' });
|
|
490
|
+
}
|
|
491
|
+
const resolutionValue = object.resolution;
|
|
492
|
+
if (!resolutionValue || typeof resolutionValue !== 'object' || Array.isArray(resolutionValue))
|
|
493
|
+
return undefined;
|
|
494
|
+
const resolutionObject = resolutionValue;
|
|
495
|
+
if (resolutionObject.strategy !== 'block' && resolutionObject.strategy !== 'degrade_operand')
|
|
496
|
+
return undefined;
|
|
497
|
+
if (typeof resolutionObject.reasonCode !== 'string' || !resolutionObject.reasonCode)
|
|
498
|
+
return undefined;
|
|
499
|
+
const operandId = resolutionObject.operandId;
|
|
500
|
+
if (resolutionObject.strategy === 'degrade_operand' && (typeof operandId !== 'string' || !operandIds.has(operandId)))
|
|
501
|
+
return undefined;
|
|
502
|
+
const resolution = { strategy: resolutionObject.strategy, reasonCode: resolutionObject.reasonCode, ...(operandId === undefined ? {} : { operandId: operandId }) };
|
|
503
|
+
const severity = normalizeImportance(object.importance);
|
|
461
504
|
const code = typeof object.code === 'string' ? object.code : `RULE_${id.toUpperCase().replaceAll(/[^A-Z0-9]+/g, '_')}`;
|
|
462
|
-
const reasonCode =
|
|
463
|
-
const message = typeof object.message === 'string' ? object.message : `Declarative rule ${id} found incompatible constraints.`;
|
|
505
|
+
const reasonCode = resolution.reasonCode;
|
|
506
|
+
const message = typeof object.explanation === 'string' ? object.explanation : typeof object.message === 'string' ? object.message : `Declarative rule ${id} found incompatible constraints.`;
|
|
464
507
|
const dependencyKind = object.dependencyKind === 'parent_detail' || object.dependencyKind === 'identity_garment' || object.dependencyKind === 'source_isolation' || object.dependencyKind === 'visibility' || object.dependencyKind === 'occludes' || object.dependencyKind === 'ordered_before' || object.dependencyKind === 'supports' || object.dependencyKind === 'excludes' || object.dependencyKind === 'requires' ? object.dependencyKind : undefined;
|
|
465
508
|
return {
|
|
466
509
|
id,
|
|
467
510
|
contributionId,
|
|
468
511
|
code,
|
|
469
512
|
kind,
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
leftTokens: sortedStrings(tokenList('leftTokens')),
|
|
473
|
-
rightTokens: sortedStrings(tokenList('rightTokens')),
|
|
513
|
+
operands,
|
|
514
|
+
resolution,
|
|
474
515
|
importance: severity,
|
|
475
516
|
reasonCode,
|
|
476
517
|
message,
|
|
@@ -479,25 +520,40 @@ function internalRule(value, contributionId) {
|
|
|
479
520
|
};
|
|
480
521
|
}
|
|
481
522
|
function allRules(effectiveScenario, collisionIds = []) {
|
|
482
|
-
const values =
|
|
523
|
+
const values = [];
|
|
483
524
|
for (const contribution of effectiveScenario?.rulePacks ?? []) {
|
|
484
525
|
const object = contribution;
|
|
485
526
|
const rules = Array.isArray(object.rules) ? object.rules : [];
|
|
486
527
|
for (const rule of rules)
|
|
487
528
|
values.push({ value: rule, contributionId: typeof object.contributionId === 'string' ? object.contributionId : undefined });
|
|
488
|
-
if (rules.length === 0)
|
|
489
|
-
values.push({ value: contribution, contributionId: typeof object.contributionId === 'string' ? object.contributionId : undefined });
|
|
490
529
|
}
|
|
491
530
|
const byId = new Map();
|
|
492
|
-
|
|
493
|
-
const rule = internalRule(entry.value, entry.contributionId);
|
|
494
|
-
if (!rule)
|
|
495
|
-
continue;
|
|
531
|
+
const add = (rule) => {
|
|
496
532
|
const prior = byId.get(rule.id);
|
|
497
|
-
|
|
533
|
+
const semantic = (value) => canonicalize(jsonReady(cleanWithout(value, 'contributionId')));
|
|
534
|
+
if (prior && semantic(rule) !== semantic(prior))
|
|
498
535
|
collisionIds.push(rule.id);
|
|
499
536
|
if (!prior || canonicalize(jsonReady(rule)) < canonicalize(jsonReady(prior)))
|
|
500
537
|
byId.set(rule.id, rule);
|
|
538
|
+
};
|
|
539
|
+
for (const value of M4_DECLARATIVE_RULE_FIXTURES) {
|
|
540
|
+
const left = Array.from(value.leftPaths);
|
|
541
|
+
const right = Array.from(value.rightPaths);
|
|
542
|
+
if (left.length === 0 || right.length === 0)
|
|
543
|
+
continue;
|
|
544
|
+
const toConditions = (paths, tokens = []) => [
|
|
545
|
+
...paths.map((path) => ({ path, operator: 'present' })),
|
|
546
|
+
...tokens.map((token) => ({ path: paths[0], operator: 'contains', value: token })),
|
|
547
|
+
];
|
|
548
|
+
const leftOperand = { id: 'left', conditions: left.map((path) => ({ path, operator: 'present' })), match: 'any' };
|
|
549
|
+
const rightOperand = { id: 'right', conditions: toConditions(right, value.rightTokens), match: 'any' };
|
|
550
|
+
add({ id: value.id, code: value.code, kind: value.ruleType === 'resource' ? 'resource' : 'occlusion', operands: [leftOperand, rightOperand], resolution: { strategy: 'block', reasonCode: value.reasonCode }, importance: value.importance, reasonCode: value.reasonCode, message: value.message, resourceId: 'resourceId' in value ? value.resourceId : undefined });
|
|
551
|
+
}
|
|
552
|
+
for (const entry of values) {
|
|
553
|
+
const rule = internalRule(entry.value, entry.contributionId);
|
|
554
|
+
if (!rule)
|
|
555
|
+
continue;
|
|
556
|
+
add(rule);
|
|
501
557
|
}
|
|
502
558
|
return sortedBy([...byId.values()], (item) => item.id);
|
|
503
559
|
}
|
|
@@ -522,12 +578,174 @@ function semanticItems(input) {
|
|
|
522
578
|
}));
|
|
523
579
|
return sortedBy([...intentItems, ...factItems], (item) => `${item.path}|${item.id}`);
|
|
524
580
|
}
|
|
581
|
+
function conditionMatches(condition, items) {
|
|
582
|
+
const candidates = items.filter((item) => pathMatches(item.path, condition.path));
|
|
583
|
+
if (condition.operator === 'absent')
|
|
584
|
+
return { matched: candidates.length === 0, items: [] };
|
|
585
|
+
if (condition.operator === 'present')
|
|
586
|
+
return { matched: candidates.length > 0, items: candidates };
|
|
587
|
+
if (condition.operator === 'equals') {
|
|
588
|
+
const matched = candidates.filter((item) => canonicalize(item.value) === canonicalize(condition.value));
|
|
589
|
+
return { matched: matched.length > 0, items: matched };
|
|
590
|
+
}
|
|
591
|
+
const value = condition.value;
|
|
592
|
+
const matched = typeof value === 'string'
|
|
593
|
+
? candidates.filter((item) => valueHasToken(item.value, [value]))
|
|
594
|
+
: candidates.filter((item) => Array.isArray(item.value) && item.value.some((entry) => canonicalize(entry) === canonicalize(value)));
|
|
595
|
+
return { matched: matched.length > 0, items: matched };
|
|
596
|
+
}
|
|
597
|
+
function compositionVocabulary(effectiveScenario, collisionPaths = []) {
|
|
598
|
+
const definitions = new Map();
|
|
599
|
+
for (const contribution of effectiveScenario?.ontologyVocabulary ?? []) {
|
|
600
|
+
for (const raw of (contribution.paths ?? [])) {
|
|
601
|
+
if (!raw || typeof raw.path !== 'string' || !raw.path)
|
|
602
|
+
continue;
|
|
603
|
+
const prior = definitions.get(raw.path);
|
|
604
|
+
if (prior && canonicalize(jsonReady(raw)) !== canonicalize(jsonReady(prior)))
|
|
605
|
+
collisionPaths.push(raw.path);
|
|
606
|
+
else if (!prior)
|
|
607
|
+
definitions.set(raw.path, clone(raw));
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return definitions;
|
|
611
|
+
}
|
|
612
|
+
function ontologyScalarMatches(definition, value) {
|
|
613
|
+
if (definition.valueKind === 'boolean')
|
|
614
|
+
return typeof value === 'boolean';
|
|
615
|
+
if (definition.valueKind === 'string')
|
|
616
|
+
return typeof value === 'string';
|
|
617
|
+
if (definition.valueKind === 'number')
|
|
618
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
619
|
+
return Array.isArray(definition.allowedValues) && definition.allowedValues.some((allowed) => canonicalize(allowed) === canonicalize(value));
|
|
620
|
+
}
|
|
621
|
+
function ontologyValueMatches(definition, value) {
|
|
622
|
+
if (definition.cardinality === 'many' && Array.isArray(value))
|
|
623
|
+
return value.every((entry) => ontologyScalarMatches(definition, entry));
|
|
624
|
+
return ontologyScalarMatches(definition, value);
|
|
625
|
+
}
|
|
626
|
+
function applyCardinalityResolution(constraints, goals, vocabulary) {
|
|
627
|
+
const remap = new Map();
|
|
628
|
+
const conflicts = [];
|
|
629
|
+
const degradations = [];
|
|
630
|
+
const traces = [];
|
|
631
|
+
const mergedConstraints = [];
|
|
632
|
+
const cardinalityConstraintIds = new Set();
|
|
633
|
+
const byPath = new Map();
|
|
634
|
+
for (const constraint of constraints) {
|
|
635
|
+
if (!constraint.targetPath || constraint.value === undefined || constraint.status === 'unsatisfied' || !vocabulary.has(constraint.targetPath))
|
|
636
|
+
continue;
|
|
637
|
+
if (vocabulary.get(constraint.targetPath).cardinality !== 'one')
|
|
638
|
+
continue;
|
|
639
|
+
byPath.set(constraint.targetPath, [...(byPath.get(constraint.targetPath) ?? []), constraint]);
|
|
640
|
+
}
|
|
641
|
+
const degraded = new Set();
|
|
642
|
+
const markUnsatisfied = (constraint, ruleId, reasonCode) => {
|
|
643
|
+
constraint.status = 'unsatisfied';
|
|
644
|
+
constraint.ruleId = ruleId;
|
|
645
|
+
constraint.reasonCode = reasonCode;
|
|
646
|
+
constraint.constraintHash = computeConstraintHash(constraint);
|
|
647
|
+
};
|
|
648
|
+
for (const [path, pathConstraints] of [...byPath.entries()].sort((left, right) => compareCodeUnits(left[0], right[0]))) {
|
|
649
|
+
for (const item of pathConstraints)
|
|
650
|
+
cardinalityConstraintIds.add(item.id);
|
|
651
|
+
const valueGroups = new Map();
|
|
652
|
+
for (const constraint of sortedBy(pathConstraints, (item) => item.id)) {
|
|
653
|
+
const key = canonicalize(jsonReady(constraint.value));
|
|
654
|
+
valueGroups.set(key, [...(valueGroups.get(key) ?? []), constraint]);
|
|
655
|
+
}
|
|
656
|
+
const merged = [];
|
|
657
|
+
for (const group of [...valueGroups.values()].sort((left, right) => compareCodeUnits(canonicalize(jsonReady(left[0].value)), canonicalize(jsonReady(right[0].value))))) {
|
|
658
|
+
const representative = sortedBy(group, (item) => item.id)[0];
|
|
659
|
+
const mergedConstraint = createConstraint({
|
|
660
|
+
...representative,
|
|
661
|
+
id: hashId('constraint-value', { path, value: representative.value }),
|
|
662
|
+
status: group.some((item) => item.status === 'active') ? 'active' : 'satisfied',
|
|
663
|
+
importance: importanceFromValues(group.map((item) => item.importance), representative.importance),
|
|
664
|
+
sourceIds: sortedStrings(group.flatMap((item) => item.sourceIds)),
|
|
665
|
+
goalIds: sortedStrings(group.flatMap((item) => item.goalIds)),
|
|
666
|
+
});
|
|
667
|
+
merged.push(mergedConstraint);
|
|
668
|
+
mergedConstraints.push(mergedConstraint);
|
|
669
|
+
for (const item of group)
|
|
670
|
+
remap.set(item.id, mergedConstraint.id);
|
|
671
|
+
}
|
|
672
|
+
for (const goal of goals)
|
|
673
|
+
goal.constraintIds = sortedStrings(goal.constraintIds.map((id) => remap.get(id) ?? id));
|
|
674
|
+
const strongGroups = merged.filter((item) => item.importance === 'hard' || item.importance === 'required');
|
|
675
|
+
const ruleId = `rule.cardinality.${path}`;
|
|
676
|
+
if (merged.length > 1) {
|
|
677
|
+
const blocking = strongGroups.length !== 1;
|
|
678
|
+
const conflict = createConstraintConflict({
|
|
679
|
+
schemaVersion: CONSTRAINT_CONFLICT_SCHEMA_VERSION,
|
|
680
|
+
id: hashId('constraint-conflict', { code: 'CARDINALITY_CONFLICT', path, constraintIds: merged.map((item) => item.id) }),
|
|
681
|
+
code: 'CARDINALITY_CONFLICT',
|
|
682
|
+
severity: importanceFromValues(merged.map((item) => item.importance), 'preferred'),
|
|
683
|
+
targetPath: path,
|
|
684
|
+
constraintIds: merged.map((item) => item.id),
|
|
685
|
+
dependencyIds: [], resourceClaimIds: [],
|
|
686
|
+
message: `Cardinality one path ${path} received incompatible values.`,
|
|
687
|
+
blocking,
|
|
688
|
+
waiverAllowed: !blocking,
|
|
689
|
+
});
|
|
690
|
+
conflicts.push(conflict);
|
|
691
|
+
if (!blocking && strongGroups.length === 1) {
|
|
692
|
+
for (const loser of merged.filter((item) => item.importance === 'preferred')) {
|
|
693
|
+
markUnsatisfied(loser, ruleId, 'CARDINALITY_PREFERENCE_DEGRADED');
|
|
694
|
+
if (degraded.has(loser.id))
|
|
695
|
+
continue;
|
|
696
|
+
degraded.add(loser.id);
|
|
697
|
+
const degradation = createDegradation({
|
|
698
|
+
schemaVersion: DEGRADATION_SCHEMA_VERSION,
|
|
699
|
+
id: hashId('degradation', { conflictId: conflict.id, constraintId: loser.id }),
|
|
700
|
+
preferenceId: loser.id,
|
|
701
|
+
constraintId: loser.id,
|
|
702
|
+
reasonCode: 'CARDINALITY_PREFERENCE_DEGRADED',
|
|
703
|
+
impact: conflict.message,
|
|
704
|
+
affectedIds: [loser.id, strongGroups[0].id],
|
|
705
|
+
explanation: `Preferred value for ${path} was excluded in favor of the stronger constraint.`,
|
|
706
|
+
});
|
|
707
|
+
degradations.push(degradation);
|
|
708
|
+
traces.push(createRuleTrace({ schemaVersion: RULE_TRACE_SCHEMA_VERSION, id: hashId('rule-trace', { ruleId, constraintId: loser.id }), ruleId, inputIds: [loser.id, strongGroups[0].id], outputIds: [degradation.id], outcome: 'degraded', reasonCode: degradation.reasonCode, message: degradation.explanation }));
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
traces.push(createRuleTrace({ schemaVersion: RULE_TRACE_SCHEMA_VERSION, id: hashId('rule-trace', { ruleId, constraintIds: merged.map((item) => item.id) }), ruleId, inputIds: merged.map((item) => item.id), outputIds: [conflict.id], outcome: 'blocked', reasonCode: 'CARDINALITY_CONFLICT', message: conflict.message }));
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
for (const goal of goals)
|
|
717
|
+
goal.constraintIds = sortedStrings(goal.constraintIds.map((id) => remap.get(id) ?? id));
|
|
718
|
+
return { constraints: sortedBy([...constraints.filter((item) => !cardinalityConstraintIds.has(item.id)), ...mergedConstraints], (item) => item.id), remap, conflicts, degradations, traces };
|
|
719
|
+
}
|
|
720
|
+
function operandMatches(operand, items) {
|
|
721
|
+
const matches = operand.conditions.map((condition) => conditionMatches(condition, items));
|
|
722
|
+
if (operand.match === 'any') {
|
|
723
|
+
const matched = matches.filter((value) => value.matched);
|
|
724
|
+
return matched.length > 0 ? uniqueSortedObjects(matched.flatMap((value) => value.items), (item) => item.id) : undefined;
|
|
725
|
+
}
|
|
726
|
+
if (matches.some((value) => !value.matched))
|
|
727
|
+
return undefined;
|
|
728
|
+
return uniqueSortedObjects(matches.flatMap((value) => value.items), (item) => item.id);
|
|
729
|
+
}
|
|
525
730
|
function ruleMatches(rule, items) {
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
|
|
731
|
+
const operands = new Map();
|
|
732
|
+
const missingOperandIds = [];
|
|
733
|
+
for (const operand of rule.operands) {
|
|
734
|
+
const match = operandMatches(operand, items);
|
|
735
|
+
if (match === undefined)
|
|
736
|
+
missingOperandIds.push(operand.id);
|
|
737
|
+
else
|
|
738
|
+
operands.set(operand.id, match);
|
|
739
|
+
}
|
|
740
|
+
if (missingOperandIds.length === 0)
|
|
741
|
+
return { operands, missingOperandIds };
|
|
742
|
+
if (rule.kind !== 'dependency')
|
|
743
|
+
return undefined;
|
|
744
|
+
const trigger = rule.operands[0];
|
|
745
|
+
const triggerMatch = operands.get(trigger.id);
|
|
746
|
+
if (triggerMatch === undefined || triggerMatch.length === 0)
|
|
529
747
|
return undefined;
|
|
530
|
-
return {
|
|
748
|
+
return { operands, missingOperandIds };
|
|
531
749
|
}
|
|
532
750
|
function goalForIntent(intent) {
|
|
533
751
|
const base = {
|
|
@@ -608,20 +826,20 @@ function outputConstraints(contract) {
|
|
|
608
826
|
function makeRuleTrace(rule, inputIds, outputIds, outcome, reasonCode = rule.reasonCode, message = rule.message) {
|
|
609
827
|
return createRuleTrace({ schemaVersion: RULE_TRACE_SCHEMA_VERSION, id: hashId('rule-trace', { ruleId: rule.id, inputIds, outputIds, outcome, reasonCode }), ruleId: rule.id, ...(rule.contributionId ? { contributionId: rule.contributionId } : {}), inputIds, outputIds, outcome, reasonCode, message });
|
|
610
828
|
}
|
|
611
|
-
function conflictForRule(rule,
|
|
612
|
-
const constraintIds = sortedStrings(
|
|
613
|
-
const severity = importanceFromValues(
|
|
829
|
+
function conflictForRule(rule, constraints, blocking = true) {
|
|
830
|
+
const constraintIds = sortedStrings(constraints.map((item) => item.id));
|
|
831
|
+
const severity = importanceFromValues(constraints.map((item) => item.importance), rule.importance);
|
|
614
832
|
return createConstraintConflict({
|
|
615
833
|
schemaVersion: CONSTRAINT_CONFLICT_SCHEMA_VERSION,
|
|
616
834
|
id: hashId('constraint-conflict', { code: rule.code, ruleId: rule.id, constraintIds }),
|
|
617
835
|
code: rule.code,
|
|
618
836
|
severity,
|
|
619
|
-
targetPath:
|
|
837
|
+
targetPath: constraints.map((item) => item.targetPath).find((item) => typeof item === 'string'),
|
|
620
838
|
constraintIds,
|
|
621
839
|
dependencyIds: [],
|
|
622
840
|
resourceClaimIds: rule.resourceId ? [hashId('resource', { resourceId: rule.resourceId, constraintIds })] : [],
|
|
623
841
|
message: rule.message,
|
|
624
|
-
blocking
|
|
842
|
+
blocking,
|
|
625
843
|
waiverAllowed: severity === 'required',
|
|
626
844
|
});
|
|
627
845
|
}
|
|
@@ -822,6 +1040,16 @@ export class ConstraintGraphCompiler {
|
|
|
822
1040
|
allRules(input.effectiveScenario, ruleCollisionIds);
|
|
823
1041
|
if (ruleCollisionIds.length)
|
|
824
1042
|
return blockedConstraintIR(input, ['DECLARATIVE_RULE_ID_COLLISION']);
|
|
1043
|
+
const vocabularyCollisionPaths = [];
|
|
1044
|
+
const vocabulary = compositionVocabulary(input.effectiveScenario, vocabularyCollisionPaths);
|
|
1045
|
+
if (vocabularyCollisionPaths.length)
|
|
1046
|
+
return blockedConstraintIR(input, ['ONTOLOGY_PATH_DEFINITION_COLLISION']);
|
|
1047
|
+
const invalidOntologyValues = semanticItems(input).filter((item) => {
|
|
1048
|
+
const definition = vocabulary.get(item.path);
|
|
1049
|
+
return definition !== undefined && !ontologyValueMatches(definition, item.value);
|
|
1050
|
+
});
|
|
1051
|
+
if (invalidOntologyValues.length)
|
|
1052
|
+
return blockedConstraintIR(input, ['ONTOLOGY_VALUE_INVALID']);
|
|
825
1053
|
const goals = [];
|
|
826
1054
|
const constraints = [];
|
|
827
1055
|
const dependencies = [];
|
|
@@ -881,32 +1109,89 @@ export class ConstraintGraphCompiler {
|
|
|
881
1109
|
const output = outputConstraints(input.outputContract);
|
|
882
1110
|
constraints.push(...output);
|
|
883
1111
|
traces.push(createRuleTrace({ schemaVersion: RULE_TRACE_SCHEMA_VERSION, id: hashId('rule-trace', { kind: 'output', id: output[0].id }), ruleId: 'rule.output-contract', inputIds: [], outputIds: output.map((item) => item.id), outcome: 'applied', reasonCode: 'OUTPUT_CONTRACT_REQUIRED', message: 'OutputContract was compiled into provider-neutral output constraints.' }));
|
|
1112
|
+
const cardinality = applyCardinalityResolution(constraints, goals, vocabulary);
|
|
1113
|
+
constraints.splice(0, constraints.length, ...cardinality.constraints);
|
|
1114
|
+
conflicts.push(...cardinality.conflicts);
|
|
1115
|
+
degradations.push(...cardinality.degradations);
|
|
1116
|
+
traces.push(...cardinality.traces);
|
|
1117
|
+
for (const goal of goals)
|
|
1118
|
+
goal.goalHash = computeGoalHash(goal);
|
|
1119
|
+
constraintByPath.clear();
|
|
1120
|
+
for (const constraint of constraints)
|
|
1121
|
+
if (constraint.targetPath)
|
|
1122
|
+
constraintByPath.set(constraint.targetPath, [...(constraintByPath.get(constraint.targetPath) ?? []), constraint]);
|
|
884
1123
|
const ruleItems = items;
|
|
1124
|
+
const degradedConstraintIds = new Set(degradations.map((item) => item.constraintId).filter((item) => typeof item === 'string'));
|
|
1125
|
+
const markUnsatisfied = (constraint, rule, reasonCode) => {
|
|
1126
|
+
constraint.status = 'unsatisfied';
|
|
1127
|
+
constraint.ruleId = rule.id;
|
|
1128
|
+
constraint.reasonCode = reasonCode;
|
|
1129
|
+
constraint.constraintHash = computeConstraintHash(constraint);
|
|
1130
|
+
};
|
|
1131
|
+
const degrade = (constraint, rule, conflict, affectedIds) => {
|
|
1132
|
+
const target = constraints.find((item) => item.id === constraint.id);
|
|
1133
|
+
if (!target || target.importance !== 'preferred' || degradedConstraintIds.has(target.id))
|
|
1134
|
+
return undefined;
|
|
1135
|
+
markUnsatisfied(target, rule, rule.resolution.reasonCode);
|
|
1136
|
+
degradedConstraintIds.add(target.id);
|
|
1137
|
+
const degradation = createDegradation({ schemaVersion: DEGRADATION_SCHEMA_VERSION, id: hashId('degradation', { conflictId: conflict.id, constraintId: target.id }), preferenceId: target.id, constraintId: target.id, reasonCode: rule.resolution.reasonCode, impact: conflict.message, affectedIds: sortedStrings([target.id, ...affectedIds]), explanation: `Preferred constraint ${target.id} was excluded because ${conflict.message}` });
|
|
1138
|
+
degradations.push(degradation);
|
|
1139
|
+
return degradation;
|
|
1140
|
+
};
|
|
885
1141
|
for (const rule of allRules(input.effectiveScenario)) {
|
|
886
1142
|
const match = ruleMatches(rule, ruleItems);
|
|
887
1143
|
if (!match) {
|
|
888
1144
|
traces.push(makeRuleTrace(rule, [], [], 'skipped', 'RULE_PRECONDITION_NOT_MET', 'Declarative rule preconditions were not met.'));
|
|
889
1145
|
continue;
|
|
890
1146
|
}
|
|
891
|
-
const
|
|
892
|
-
const
|
|
1147
|
+
const matchedItems = [...match.operands.values()].flat();
|
|
1148
|
+
const matchedConstraints = uniqueSortedObjects(matchedItems.flatMap((item) => constraintByPath.get(item.path) ?? []), (item) => item.id);
|
|
1149
|
+
const inputIds = sortedStrings(matchedItems.map((item) => item.id));
|
|
893
1150
|
if (rule.kind === 'dependency') {
|
|
894
|
-
const
|
|
895
|
-
const
|
|
896
|
-
if (
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
1151
|
+
const parentItems = match.operands.get(rule.operands[0].id) ?? [];
|
|
1152
|
+
const parentConstraints = uniqueSortedObjects(parentItems.flatMap((item) => constraintByPath.get(item.path) ?? []), (item) => item.id);
|
|
1153
|
+
if (match.missingOperandIds.length > 0) {
|
|
1154
|
+
const dependencyConflict = createConstraintConflict({ schemaVersion: CONSTRAINT_CONFLICT_SCHEMA_VERSION, id: hashId('constraint-conflict', { code: 'CONSTRAINT_DEPENDENCY_MISSING', ruleId: rule.id, constraintIds: parentConstraints.map((item) => item.id), missingOperandIds: match.missingOperandIds }), code: 'CONSTRAINT_DEPENDENCY_MISSING', severity: importanceFromValues(parentConstraints.map((item) => item.importance), rule.importance), targetPath: parentConstraints[0]?.targetPath, constraintIds: parentConstraints.map((item) => item.id), dependencyIds: [], resourceClaimIds: [], message: `${rule.message} Required dependency operand(s) missing: ${match.missingOperandIds.join(', ')}.`, blocking: !(rule.resolution.strategy === 'degrade_operand' && parentConstraints.every((item) => item.importance === 'preferred')), waiverAllowed: rule.importance === 'required' });
|
|
1155
|
+
conflicts.push(dependencyConflict);
|
|
1156
|
+
const dependencyDegradations = rule.resolution.strategy === 'degrade_operand' ? parentConstraints.map((constraint) => degrade(constraint, rule, dependencyConflict, parentConstraints.map((item) => item.id))).filter((item) => item !== undefined).map((item) => item.id) : [];
|
|
1157
|
+
traces.push(makeRuleTrace(rule, inputIds, [dependencyConflict.id, ...dependencyDegradations], dependencyConflict.blocking ? 'blocked' : 'degraded', 'CONSTRAINT_DEPENDENCY_MISSING', dependencyConflict.message));
|
|
1158
|
+
}
|
|
1159
|
+
else {
|
|
1160
|
+
const dependencyIds = [];
|
|
1161
|
+
for (const operand of rule.operands.slice(1)) {
|
|
1162
|
+
const childItems = match.operands.get(operand.id) ?? [];
|
|
1163
|
+
const child = uniqueSortedObjects(childItems.flatMap((item) => constraintByPath.get(item.path) ?? []), (item) => item.id)[0];
|
|
1164
|
+
const parent = parentConstraints[0];
|
|
1165
|
+
if (!parent || !child)
|
|
1166
|
+
continue;
|
|
1167
|
+
const dependency = createConstraintDependency({ schemaVersion: CONSTRAINT_DEPENDENCY_SCHEMA_VERSION, id: hashId('constraint-dependency', { ruleId: rule.id, parent: parent.id, child: child.id }), parentId: parent.id, childId: child.id, kind: rule.dependencyKind ?? 'requires', importance: rule.importance, explanation: rule.message });
|
|
1168
|
+
dependencies.push(dependency);
|
|
1169
|
+
dependencyIds.push(dependency.id);
|
|
1170
|
+
}
|
|
1171
|
+
traces.push(makeRuleTrace(rule, inputIds, dependencyIds, 'applied', rule.reasonCode));
|
|
900
1172
|
}
|
|
901
1173
|
continue;
|
|
902
1174
|
}
|
|
903
|
-
|
|
1175
|
+
if (matchedConstraints.length < 2) {
|
|
1176
|
+
traces.push(makeRuleTrace(rule, inputIds, [], 'skipped', 'RULE_SINGLE_OPERAND_MATCH', 'A declarative rule matched fewer than two constraint operands.'));
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
const strong = matchedConstraints.filter((constraint) => constraint.importance === 'hard' || constraint.importance === 'required');
|
|
1180
|
+
let degradable = matchedConstraints.filter((constraint) => constraint.importance === 'preferred');
|
|
1181
|
+
if (strong.length === 0 && rule.resolution.strategy === 'degrade_operand' && rule.resolution.operandId) {
|
|
1182
|
+
const targetedItems = match.operands.get(rule.resolution.operandId) ?? [];
|
|
1183
|
+
const targetedIds = new Set(targetedItems.map((item) => item.id));
|
|
1184
|
+
degradable = matchedConstraints.filter((constraint) => constraint.importance === 'preferred' && constraint.sourceIds.some((sourceId) => targetedIds.has(sourceId)) || constraint.importance === 'preferred' && targetedItems.some((item) => item.path === constraint.targetPath));
|
|
1185
|
+
}
|
|
1186
|
+
const autoDegrade = strong.length === 1 ? matchedConstraints.filter((constraint) => constraint.importance === 'preferred') : strong.length === 0 && degradable.length > 0 && degradable.length < matchedConstraints.length ? degradable : [];
|
|
1187
|
+
const conflict = conflictForRule(rule, matchedConstraints, autoDegrade.length === 0);
|
|
904
1188
|
conflicts.push(conflict);
|
|
1189
|
+
const degradationIds = autoDegrade.map((constraint) => degrade(constraint, rule, conflict, strong.map((item) => item.id))).filter((item) => item !== undefined).map((item) => item.id);
|
|
905
1190
|
if (rule.resourceId) {
|
|
906
|
-
const claim = createResourceClaim({ schemaVersion: RESOURCE_CLAIM_SCHEMA_VERSION, id: conflict.resourceClaimIds[0] ?? hashId('resource', { resourceId: rule.resourceId, constraintIds: conflict.constraintIds }), resourceId: rule.resourceId, mode: 'exclusive', claimantIds:
|
|
1191
|
+
const claim = createResourceClaim({ schemaVersion: RESOURCE_CLAIM_SCHEMA_VERSION, id: conflict.resourceClaimIds[0] ?? hashId('resource', { resourceId: rule.resourceId, constraintIds: conflict.constraintIds }), resourceId: rule.resourceId, mode: 'exclusive', claimantIds: inputIds, constraintIds: conflict.constraintIds, quantity: 1, explanation: rule.message });
|
|
907
1192
|
resourceClaims.push(claim);
|
|
908
1193
|
}
|
|
909
|
-
traces.push(makeRuleTrace(rule, [
|
|
1194
|
+
traces.push(makeRuleTrace(rule, inputIds, [conflict.id, ...degradationIds], conflict.blocking ? 'blocked' : 'degraded', conflict.code));
|
|
910
1195
|
}
|
|
911
1196
|
const allConstraintIds = new Set(constraints.map((constraint) => constraint.id));
|
|
912
1197
|
for (const dependency of dependencies) {
|
|
@@ -936,12 +1221,6 @@ export class ConstraintGraphCompiler {
|
|
|
936
1221
|
adjustedConflicts.push(rehashConflict({ ...conflict, blocking: false, message: `${conflict.message} Proceeding only under an explicit scoped waiver.` }));
|
|
937
1222
|
warnings.push('REQUIRED_CONFLICT_WAIVED');
|
|
938
1223
|
}
|
|
939
|
-
else if (conflict.severity === 'preferred') {
|
|
940
|
-
adjustedConflicts.push(rehashConflict({ ...conflict, blocking: false }));
|
|
941
|
-
const degradation = createDegradation({ schemaVersion: DEGRADATION_SCHEMA_VERSION, id: hashId('degradation', { conflictId: conflict.id }), preferenceId: conflict.id, constraintId: conflict.constraintIds[0], reasonCode: conflict.code, impact: conflict.message, affectedIds: conflict.constraintIds, explanation: `Preferred constraint was degraded deterministically because ${conflict.message}` });
|
|
942
|
-
degradations.push(degradation);
|
|
943
|
-
traces.push(createRuleTrace({ schemaVersion: RULE_TRACE_SCHEMA_VERSION, id: hashId('rule-trace', { degradation: degradation.id }), ruleId: 'rule.preferred-degradation', inputIds: conflict.constraintIds, outputIds: [degradation.id], outcome: 'degraded', reasonCode: conflict.code, message: degradation.explanation }));
|
|
944
|
-
}
|
|
945
1224
|
else
|
|
946
1225
|
adjustedConflicts.push(conflict);
|
|
947
1226
|
}
|
package/dist/m5.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ArtifactHandle, ArtifactReplayResult, CompilationContext, CompensationReceipt, ConstraintIR, CleanupReceipt, Evaluation, ExecutionAuthorization, ExecutionRun, ExecutionTraceProjection, HumanAcceptance, JsonObject, PipelinePlan, PipelineStep, PromptCandidateIR, PromptCompilationInput, PromptGuardInput, PromptGuardResult, PromptIR, PromptOptimizationInput, PromptOptimizer, PromptTransformation, ProviderAdapter, ProviderRenderRequest, ProviderRenderResult, ReferencePlan, RemoteCallRun, RemoteCallAuthorization, StepReceipt, VersionPin } from '@voce-engine/contracts';
|
|
1
|
+
import type { ArtifactHandle, ArtifactReplayResult, CompilationContext, CompensationReceipt, ConstraintIR, CleanupReceipt, Evaluation, ExecutionAuthorization, ExecutionRun, ExecutionRunState, ExecutionTraceProjection, HumanAcceptance, JsonObject, PipelinePlan, PipelineStep, PromptCandidateIR, PromptCompilationInput, PromptGuardInput, PromptGuardResult, PromptIR, PromptOptimizationInput, PromptOptimizer, PromptTransformation, ProviderAdapter, ProviderRenderRequest, ProviderRenderResult, ReferencePlan, RemoteCallRun, RemoteCallAuthorization, StepReceipt, VersionPin } from '@voce-engine/contracts';
|
|
2
2
|
export declare const PROMPT_COMPILER_VERSION = "voce.prompt-compiler/v1alpha1";
|
|
3
3
|
export declare const PROMPT_OPTIMIZER_VERSION = "voce.deterministic-prompt-optimizer/v1alpha1";
|
|
4
4
|
export declare const PROMPT_GUARD_VERSION = "voce.prompt-guard/v1alpha1";
|
|
@@ -75,7 +75,7 @@ export interface OfflineExecutionInput {
|
|
|
75
75
|
options?: OfflineExecutionOptions;
|
|
76
76
|
}
|
|
77
77
|
export interface OfflineExecutionResult {
|
|
78
|
-
status: 'blocked' |
|
|
78
|
+
status: 'blocked' | ExecutionRunState;
|
|
79
79
|
code: string;
|
|
80
80
|
reasons: string[];
|
|
81
81
|
executionRun?: ExecutionRun;
|
package/dist/m5.js
CHANGED
|
@@ -152,6 +152,7 @@ function normalizedPromptIRProjection(prompt) {
|
|
|
152
152
|
forbidden: sortedBy(prompt.forbidden, (item) => item.id),
|
|
153
153
|
output: clone(prompt.output),
|
|
154
154
|
constraintCoverage: sortedBy(prompt.constraintCoverage, (item) => item.constraintId).map(promptCoverageProjection),
|
|
155
|
+
excludedConstraints: sortedBy(prompt.excludedConstraints, (item) => item.constraintId),
|
|
155
156
|
sourceIds: sortedStrings(prompt.sourceIds),
|
|
156
157
|
constraintIds: sortedStrings(prompt.constraintIds),
|
|
157
158
|
decisionIds: sortedStrings(prompt.decisionIds),
|
|
@@ -230,6 +231,7 @@ function normalizedPromptCandidateProjection(candidate) {
|
|
|
230
231
|
parameters: sortedBy(parameters, (item) => item.id).map(promptParameterProjection),
|
|
231
232
|
referenceMappings: [...candidate.referenceMappings].sort((left, right) => left.order - right.order || compareCodeUnits(left.id, right.id)).map(promptReferenceMappingProjection),
|
|
232
233
|
constraintCoverage: sortedBy(coverage, (item) => item.constraintId).map(promptCoverageProjection),
|
|
234
|
+
excludedConstraints: sortedBy(candidate.excludedConstraints, (item) => item.constraintId),
|
|
233
235
|
transformations: candidate.transformations.map(transformationProjection),
|
|
234
236
|
optimizer: clone(candidate.optimizer),
|
|
235
237
|
mode: candidate.mode,
|
|
@@ -337,6 +339,24 @@ function integrityReasonsForConstraintIR(ir, context, caseId, caseRevision) {
|
|
|
337
339
|
for (const item of ir.degradedPreferences)
|
|
338
340
|
if (!isHash(item.degradationHash) || computeDegradationHash(item) !== item.degradationHash)
|
|
339
341
|
reasons.push('DEGRADATION_HASH_MISMATCH');
|
|
342
|
+
const degradationCounts = new Map();
|
|
343
|
+
for (const degradation of ir.degradedPreferences) {
|
|
344
|
+
if (!degradation.constraintId) {
|
|
345
|
+
reasons.push('DEGRADATION_CONSTRAINT_MISSING');
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
degradationCounts.set(degradation.constraintId, (degradationCounts.get(degradation.constraintId) ?? 0) + 1);
|
|
349
|
+
const constraint = ir.constraints.find((item) => item.id === degradation.constraintId);
|
|
350
|
+
if (!constraint || constraint.importance !== 'preferred' || constraint.status !== 'unsatisfied')
|
|
351
|
+
reasons.push('DEGRADATION_DISPOSITION_INVALID');
|
|
352
|
+
}
|
|
353
|
+
for (const constraint of ir.constraints) {
|
|
354
|
+
const count = degradationCounts.get(constraint.id) ?? 0;
|
|
355
|
+
if (constraint.status === 'unsatisfied' && count !== 1)
|
|
356
|
+
reasons.push('UNSATISFIED_CONSTRAINT_DEGRADATION_MISMATCH');
|
|
357
|
+
if (constraint.status !== 'unsatisfied' && count > 0)
|
|
358
|
+
reasons.push('DEGRADATION_DISPOSITION_INVALID');
|
|
359
|
+
}
|
|
340
360
|
for (const item of ir.reviewRequirements)
|
|
341
361
|
if (!isHash(item.reviewHash) || computeReviewRequirementHash(item) !== item.reviewHash)
|
|
342
362
|
reasons.push('REVIEW_REQUIREMENT_HASH_MISMATCH');
|
|
@@ -420,22 +440,57 @@ function promptCompilationInputReasons(input) {
|
|
|
420
440
|
reasons.push('TARGET_ADAPTER_INVALID');
|
|
421
441
|
if (!input.targetCapabilityProfile || !isHash(input.targetCapabilityProfile.digest))
|
|
422
442
|
reasons.push('TARGET_PROFILE_INVALID');
|
|
443
|
+
if (!input.effectiveScenario || !isHash(input.context?.effectiveScenarioHash ?? '') || input.effectiveScenario?.effectiveScenarioHash !== input.context?.effectiveScenarioHash)
|
|
444
|
+
reasons.push('EFFECTIVE_SCENARIO_CONTEXT_MISMATCH');
|
|
445
|
+
if (input.effectiveScenario) {
|
|
446
|
+
const scenarioProjection = clone(input.effectiveScenario);
|
|
447
|
+
delete scenarioProjection.effectiveScenarioHash;
|
|
448
|
+
if (!isHash(input.effectiveScenario.effectiveScenarioHash) || sha256(jsonReady(scenarioProjection)) !== input.effectiveScenario.effectiveScenarioHash)
|
|
449
|
+
reasons.push('EFFECTIVE_SCENARIO_HASH_MISMATCH');
|
|
450
|
+
const definitions = input.effectiveScenario.promptSections.flatMap((contribution) => contribution.sections ?? []);
|
|
451
|
+
const definitionIds = new Set();
|
|
452
|
+
for (const definition of definitions) {
|
|
453
|
+
if (definitionIds.has(definition.id) || !Number.isInteger(definition.order) || !Array.isArray(definition.pathPrefixes) || definition.pathPrefixes.length === 0 || typeof definition.templateKey !== 'string')
|
|
454
|
+
reasons.push('PROMPT_SECTION_POLICY_INVALID');
|
|
455
|
+
definitionIds.add(definition.id);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
423
458
|
if (input.pipelinePlan && input.pipelinePlan.profileDigest !== input.targetCapabilityProfile?.digest)
|
|
424
459
|
reasons.push('TARGET_PROFILE_PLAN_MISMATCH');
|
|
425
460
|
if (input.pipelinePlan && !input.pipelinePlan.adapterDigests.includes(input.targetAdapter?.digest ?? ''))
|
|
426
461
|
reasons.push('TARGET_ADAPTER_PLAN_MISMATCH');
|
|
427
462
|
return sortedStrings(reasons);
|
|
428
463
|
}
|
|
429
|
-
function
|
|
464
|
+
function pathRelates(left, right) {
|
|
465
|
+
return left === right || left.startsWith(`${right}.`) || right.startsWith(`${left}.`);
|
|
466
|
+
}
|
|
467
|
+
function promptSectionDefinitions(scenario) {
|
|
468
|
+
const definitions = scenario.promptSections.flatMap((contribution) => contribution.sections ?? []).map((definition) => clone(definition));
|
|
469
|
+
return definitions.sort((left, right) => left.order - right.order || compareCodeUnits(left.id, right.id));
|
|
470
|
+
}
|
|
471
|
+
function sectionDefinitionForConstraint(constraint, definitions) {
|
|
472
|
+
const paths = constraint.targetPaths.length ? constraint.targetPaths : constraint.targetPath ? [constraint.targetPath] : [];
|
|
473
|
+
return definitions.find((definition) => definition.pathPrefixes.some((prefix) => paths.some((path) => pathRelates(path, prefix))));
|
|
474
|
+
}
|
|
475
|
+
function promptExclusions(constraintIR) {
|
|
476
|
+
return sortedBy(constraintIR.constraints.filter((constraint) => constraint.status === 'unsatisfied').map((constraint) => {
|
|
477
|
+
const degradations = constraintIR.degradedPreferences.filter((item) => item.constraintId === constraint.id);
|
|
478
|
+
if (degradations.length !== 1)
|
|
479
|
+
throw new Error('UNSATISFIED_CONSTRAINT_DEGRADATION_MISMATCH');
|
|
480
|
+
return { constraintId: constraint.id, degradationId: degradations[0].id, reasonCode: degradations[0].reasonCode, sourceIds: sortedStrings(constraint.sourceIds) };
|
|
481
|
+
}), (item) => item.constraintId);
|
|
482
|
+
}
|
|
483
|
+
function sectionForConstraint(constraint, order, decisionIds, definition) {
|
|
430
484
|
const locked = constraint.importance === 'hard' || constraint.importance === 'required' || constraint.kind === 'output';
|
|
485
|
+
const content = `${definition?.templateKey ? `${definition.templateKey}: ` : ''}${constraintText(constraint)}`;
|
|
431
486
|
return {
|
|
432
487
|
schemaVersion: PROMPT_SECTION_SCHEMA_VERSION,
|
|
433
|
-
id: hashId('prompt-section', { kind: constraint.importance, constraintId: constraint.id, order }),
|
|
488
|
+
id: hashId('prompt-section', { kind: constraint.importance, constraintId: constraint.id, order, definitionId: definition?.id }),
|
|
434
489
|
kind: constraint.importance === 'hard' ? 'hard_constraint' : constraint.importance === 'required' ? 'required_constraint' : 'preferred',
|
|
435
490
|
priority: constraint.importance === 'hard' ? 100 : constraint.importance === 'required' ? 80 : 40,
|
|
436
491
|
order,
|
|
437
|
-
content
|
|
438
|
-
text:
|
|
492
|
+
content,
|
|
493
|
+
text: content,
|
|
439
494
|
constraintIds: [constraint.id],
|
|
440
495
|
sourceIds: sortedStrings(constraint.sourceIds),
|
|
441
496
|
decisionIds: sortedStrings(decisionIds),
|
|
@@ -446,7 +501,8 @@ function sectionForConstraint(constraint, order, decisionIds) {
|
|
|
446
501
|
};
|
|
447
502
|
}
|
|
448
503
|
function referenceMapping(reference, constraints, decisionIds) {
|
|
449
|
-
const
|
|
504
|
+
const effectiveConstraintIds = sortedStrings(reference.constraintIds.filter((id) => constraints.some((constraint) => constraint.id === id)));
|
|
505
|
+
const required = effectiveConstraintIds.some((id) => constraints.find((constraint) => constraint.id === id)?.importance !== 'preferred') || reference.sourceBindingIds.length > 0;
|
|
450
506
|
return {
|
|
451
507
|
schemaVersion: PROMPT_REFERENCE_MAPPING_SCHEMA_VERSION,
|
|
452
508
|
id: hashId('prompt-reference-mapping', { plannedReferenceId: reference.id, assetId: reference.assetId, contentHash: reference.contentHash, order: reference.order }),
|
|
@@ -458,7 +514,7 @@ function referenceMapping(reference, constraints, decisionIds) {
|
|
|
458
514
|
role: reference.role,
|
|
459
515
|
order: reference.order,
|
|
460
516
|
required,
|
|
461
|
-
constraintIds:
|
|
517
|
+
constraintIds: effectiveConstraintIds,
|
|
462
518
|
sourceBindingIds: sortedStrings(reference.sourceBindingIds),
|
|
463
519
|
decisionIds: sortedStrings(decisionIds),
|
|
464
520
|
};
|
|
@@ -505,7 +561,9 @@ export class PromptCompiler {
|
|
|
505
561
|
if (reasons.length)
|
|
506
562
|
throw new Error(reasons.join('|'));
|
|
507
563
|
const decisionIds = sortedStrings(safeInput.context.decisionHashes);
|
|
508
|
-
const
|
|
564
|
+
const excludedConstraints = promptExclusions(safeInput.constraintIR);
|
|
565
|
+
const constraints = sortedBy(safeInput.constraintIR.constraints.filter((constraint) => constraint.status === 'active' || constraint.status === 'satisfied'), (item) => item.id);
|
|
566
|
+
const definitions = promptSectionDefinitions(safeInput.effectiveScenario);
|
|
509
567
|
const sections = [];
|
|
510
568
|
const objective = safeInput.objective ?? 'Produce the requested visual result using only the approved constraints and references.';
|
|
511
569
|
const positiveDescription = safeInput.positiveDescription ?? 'Express the approved target properties clearly and preserve all locked requirements.';
|
|
@@ -521,7 +579,11 @@ export class PromptCompiler {
|
|
|
521
579
|
kind: 'positive', priority: 110, order: 1, content: positiveDescription, text: positiveDescription,
|
|
522
580
|
constraintIds: [], sourceIds: [], decisionIds, assetIds: [], importance: 'required', mutability: 'rephraseable', locked: false,
|
|
523
581
|
});
|
|
524
|
-
constraints.forEach((constraint, index) =>
|
|
582
|
+
constraints.forEach((constraint, index) => {
|
|
583
|
+
const definition = sectionDefinitionForConstraint(constraint, definitions);
|
|
584
|
+
const policyOrder = definition?.order ?? 500;
|
|
585
|
+
sections.push(sectionForConstraint(constraint, 10 + policyOrder * 10 + index, decisionIds, definition));
|
|
586
|
+
});
|
|
525
587
|
const mappings = [...safeInput.referencePlan.ordered].sort((left, right) => left.order - right.order || compareCodeUnits(left.id, right.id)).map((reference) => referenceMapping(reference, constraints, decisionIds));
|
|
526
588
|
mappings.forEach((mapping, index) => sections.push({
|
|
527
589
|
schemaVersion: PROMPT_SECTION_SCHEMA_VERSION,
|
|
@@ -566,6 +628,7 @@ export class PromptCompiler {
|
|
|
566
628
|
forbidden: sortedBy(forbidden, (item) => item.id),
|
|
567
629
|
output: clone(safeInput.outputContract),
|
|
568
630
|
constraintCoverage: sortedBy(coverage, (item) => item.constraintId),
|
|
631
|
+
excludedConstraints,
|
|
569
632
|
sourceIds: sortedStrings([...constraints.flatMap((constraint) => constraint.sourceIds), ...mappings.flatMap((mapping) => mapping.sourceBindingIds)]),
|
|
570
633
|
constraintIds: sortedStrings(constraints.map((constraint) => constraint.id)),
|
|
571
634
|
decisionIds,
|
|
@@ -654,6 +717,7 @@ export function createPromptCandidateIR(prompt, transformations = [], options =
|
|
|
654
717
|
parameters: clone(parameters),
|
|
655
718
|
referenceMappings: clone(safePrompt.referenceMappings),
|
|
656
719
|
constraintCoverage: clone(safePrompt.constraintCoverage),
|
|
720
|
+
excludedConstraints: clone(safePrompt.excludedConstraints),
|
|
657
721
|
transformations: normalizedTransformations,
|
|
658
722
|
optimizer: clone(options.optimizer ?? { id: PROMPT_OPTIMIZER_VERSION, version: '1.0.0', digest: sha256({ optimizer: PROMPT_OPTIMIZER_VERSION }) }),
|
|
659
723
|
mode: options.mode ?? 'strict',
|
|
@@ -733,7 +797,7 @@ function guardFinding(value) {
|
|
|
733
797
|
}
|
|
734
798
|
function guardInputReasons(input) {
|
|
735
799
|
const reasons = [];
|
|
736
|
-
if (!input || input.schemaVersion !== 'voce.prompt-guard-input/
|
|
800
|
+
if (!input || input.schemaVersion !== 'voce.prompt-guard-input/v1alpha2')
|
|
737
801
|
reasons.push('PROMPT_GUARD_INPUT_SCHEMA_INVALID');
|
|
738
802
|
if (!input.promptIR || input.promptIR.schemaVersion !== PROMPT_IR_SCHEMA_VERSION || !isHash(input.promptIR.deterministicSignature) || computePromptIRHash(input.promptIR) !== input.promptIR.deterministicSignature)
|
|
739
803
|
reasons.push('PROMPT_IR_SIGNATURE_MISMATCH');
|
|
@@ -751,6 +815,19 @@ function guardInputReasons(input) {
|
|
|
751
815
|
reasons.push('PROMPT_CONTEXT_MISMATCH');
|
|
752
816
|
if (input.constraintIR && integrityReasonsForConstraintIR(input.constraintIR, input.context, input.promptIR?.caseId ?? '', input.promptIR?.caseRevision ?? -1).length)
|
|
753
817
|
reasons.push('CONSTRAINT_IR_INVALID');
|
|
818
|
+
if (input.promptIR && input.constraintIR) {
|
|
819
|
+
const excludedIds = new Set(input.promptIR.excludedConstraints.map((item) => item.constraintId));
|
|
820
|
+
if (excludedIds.size !== input.promptIR.excludedConstraints.length)
|
|
821
|
+
reasons.push('PROMPT_EXCLUSION_DUPLICATE');
|
|
822
|
+
for (const exclusion of input.promptIR.excludedConstraints) {
|
|
823
|
+
const constraint = input.constraintIR.constraints.find((item) => item.id === exclusion.constraintId);
|
|
824
|
+
const degradation = input.constraintIR.degradedPreferences.find((item) => item.id === exclusion.degradationId && item.constraintId === exclusion.constraintId);
|
|
825
|
+
if (!constraint || constraint.status !== 'unsatisfied' || !degradation || input.promptIR.constraintIds.includes(exclusion.constraintId))
|
|
826
|
+
reasons.push('PROMPT_EXCLUSION_INVALID');
|
|
827
|
+
}
|
|
828
|
+
if (input.constraintIR.constraints.some((constraint) => constraint.status === 'unsatisfied' && !excludedIds.has(constraint.id)))
|
|
829
|
+
reasons.push('PROMPT_EXCLUSION_MISSING');
|
|
830
|
+
}
|
|
754
831
|
if (input.referencePlan && input.constraintIR && integrityReasonsForReferencePlan(input.referencePlan, input.constraintIR, input.promptIR?.caseId ?? '', input.promptIR?.caseRevision ?? -1, input.context?.contextHash ?? '').length)
|
|
755
832
|
reasons.push('REFERENCE_PLAN_INVALID');
|
|
756
833
|
if (input.pipelinePlan && input.constraintIR && input.referencePlan && integrityReasonsForPipelinePlan(input.pipelinePlan, input.constraintIR, input.referencePlan, input.outputContract, input.promptIR?.caseId ?? '', input.promptIR?.caseRevision ?? -1, input.context?.contextHash ?? '').length)
|
|
@@ -806,6 +883,24 @@ export class PromptGuard {
|
|
|
806
883
|
const candidateMappings = candidate.referenceMappings;
|
|
807
884
|
const baseCoverage = new Map(prompt.constraintCoverage.map((coverage) => [coverage.constraintId, coverage]));
|
|
808
885
|
const candidateCoverageMap = new Map(candidateCoverage(candidate).map((coverage) => [coverage.constraintId, coverage]));
|
|
886
|
+
const excludedIds = new Set(prompt.excludedConstraints.map((item) => item.constraintId));
|
|
887
|
+
if (canonicalize(sortedBy(candidate.excludedConstraints, (item) => item.constraintId)) !== canonicalize(sortedBy(prompt.excludedConstraints, (item) => item.constraintId)))
|
|
888
|
+
addGuardFinding(findings, { code: 'PROMPT_EXCLUSION_SET_CHANGED', severity: 'critical', blocking: true, constraintIds: [...excludedIds], sourceIds: [], sectionIds: [], decisionIds: [], assetIds: [], explanation: 'Candidate changed the explicit Prompt IR exclusion set.' });
|
|
889
|
+
const reportExcludedLinks = (ids, sectionIds = []) => {
|
|
890
|
+
const linked = ids.filter((id) => excludedIds.has(id));
|
|
891
|
+
if (linked.length)
|
|
892
|
+
addGuardFinding(findings, { code: 'EXCLUDED_CONSTRAINT_REINTRODUCED', severity: 'critical', blocking: true, constraintIds: sortedStrings(linked), sourceIds: [], sectionIds, decisionIds: [], assetIds: [], explanation: 'Candidate re-linked an excluded constraint into prompt structure.' });
|
|
893
|
+
};
|
|
894
|
+
for (const section of candidateSectionList)
|
|
895
|
+
reportExcludedLinks(section.constraintIds, [section.id]);
|
|
896
|
+
for (const parameter of candidateParameterList)
|
|
897
|
+
reportExcludedLinks(parameter.constraintIds);
|
|
898
|
+
for (const mapping of candidateMappings)
|
|
899
|
+
reportExcludedLinks(mapping.constraintIds);
|
|
900
|
+
for (const coverage of candidateCoverage(candidate))
|
|
901
|
+
reportExcludedLinks([coverage.constraintId], coverage.sectionIds);
|
|
902
|
+
for (const transformation of candidate.transformations)
|
|
903
|
+
reportExcludedLinks('proof' in transformation ? transformation.proof?.preservedConstraintIds ?? [] : [], transformation.kind === 'rephrase' ? [transformation.sectionId] : []);
|
|
809
904
|
for (const section of prompt.sections) {
|
|
810
905
|
const candidateSection = candidateSectionMap.get(section.id);
|
|
811
906
|
const locked = section.locked === true || section.mutability === 'locked' || section.kind === 'hard_constraint' || section.kind === 'required_constraint' || section.kind === 'output' || section.kind === 'reference';
|
package/dist/m6.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ export declare class RecordingMockTransport implements ProviderTransport {
|
|
|
52
52
|
enqueue(response: ProviderResponseEnvelope): void;
|
|
53
53
|
enqueueLookup(response: ProviderResponseEnvelope): void;
|
|
54
54
|
private consume;
|
|
55
|
+
private consumeResponse;
|
|
55
56
|
send(request: ProviderRequestEnvelope, context: ProviderTransportContext): Promise<ProviderResponseEnvelope>;
|
|
56
57
|
lookup(request: ProviderSubmissionLookup, context: ProviderTransportContext): Promise<ProviderResponseEnvelope>;
|
|
57
58
|
}
|
package/dist/m6.js
CHANGED
|
@@ -436,6 +436,15 @@ export class RecordingMockTransport {
|
|
|
436
436
|
throw new ProviderTransportError('REMOTE_CALL_BUDGET_EXHAUSTED', `Remote ${kind} budget has been exhausted.`);
|
|
437
437
|
counts.set(key, count + 1);
|
|
438
438
|
}
|
|
439
|
+
consumeResponse(queued, requestHash, missingCode, missingMessage) {
|
|
440
|
+
const exactIndex = queued.findIndex((item) => item.requestHash === requestHash);
|
|
441
|
+
if (exactIndex >= 0)
|
|
442
|
+
return queued.splice(exactIndex, 1)[0];
|
|
443
|
+
const wildcardIndex = queued.findIndex((item) => item.requestHash === '');
|
|
444
|
+
if (wildcardIndex >= 0)
|
|
445
|
+
return queued.splice(wildcardIndex, 1)[0];
|
|
446
|
+
return errorResponse(requestHash, missingCode, missingMessage);
|
|
447
|
+
}
|
|
439
448
|
async send(request, context) {
|
|
440
449
|
assertRemoteCallAuthorization(request, context);
|
|
441
450
|
this.consume('send', context.authorization);
|
|
@@ -445,7 +454,7 @@ export class RecordingMockTransport {
|
|
|
445
454
|
throw new ProviderTransportError('REMOTE_CALL_IDENTITY_REUSED', 'Idempotency identity cannot be reused for a different provider request.');
|
|
446
455
|
this.sentRequests.set(identity, clone(request));
|
|
447
456
|
this.calls.push({ requestId: request.id, requestHash: request.requestHash, adapterId: request.adapterId, profileDigest: request.profileDigest, destination: request.destination, ...(request.region === undefined ? {} : { region: request.region }), inputHash: request.inputHash, idempotencyKey: request.idempotencyKey });
|
|
448
|
-
const response = this.responses
|
|
457
|
+
const response = this.consumeResponse(this.responses, request.requestHash, 'MOCK_RESPONSE_MISSING', 'Recording mock response was not registered.');
|
|
449
458
|
const normalized = response.requestHash === request.requestHash ? response : { ...response, requestHash: request.requestHash, responseHash: '' };
|
|
450
459
|
if (!normalized.responseHash)
|
|
451
460
|
normalized.responseHash = computeProviderResponseEnvelopeHash(normalized);
|
|
@@ -459,7 +468,7 @@ export class RecordingMockTransport {
|
|
|
459
468
|
if (!original || !providerBindingMatches(original, request))
|
|
460
469
|
throw new ProviderTransportError('PROVIDER_LOOKUP_BINDING_MISMATCH', 'Submission lookup is not bound to a prior provider request.');
|
|
461
470
|
this.lookupCalls.push({ requestId: request.providerRequestId ?? request.lookupHash, requestHash: request.requestHash, adapterId: request.adapterId, profileDigest: request.profileDigest, destination: request.destination, ...(request.region === undefined ? {} : { region: request.region }), inputHash: request.inputHash, idempotencyKey: request.idempotencyKey });
|
|
462
|
-
const response = this.lookups
|
|
471
|
+
const response = this.consumeResponse(this.lookups, request.requestHash, 'MOCK_LOOKUP_RESPONSE_MISSING', 'Recording lookup response was not registered.');
|
|
463
472
|
const normalized = response.requestHash === request.requestHash ? response : { ...response, requestHash: request.requestHash, responseHash: '' };
|
|
464
473
|
if (!normalized.responseHash)
|
|
465
474
|
normalized.responseHash = computeProviderResponseEnvelopeHash(normalized);
|
|
@@ -1053,11 +1062,14 @@ export function compileEvaluationReport(input) {
|
|
|
1053
1062
|
const human = input.humanAcceptance ? normalizeHumanAcceptance(input.humanAcceptance, input.run.id) : undefined;
|
|
1054
1063
|
if (human && (human.annotations.some((annotation) => humanAnnotationHash(annotation) !== annotation.annotationHash) || computeHumanAcceptanceDecisionHash(human) !== human.decisionHash))
|
|
1055
1064
|
throw new ProviderTransportError('HUMAN_DECISION_HASH_MISMATCH', 'Human acceptance decision hash is invalid.');
|
|
1065
|
+
if (human && human.runId !== input.run.id)
|
|
1066
|
+
throw new ProviderTransportError('HUMAN_DECISION_RUN_MISMATCH', 'Human acceptance decision belongs to a different execution run.');
|
|
1056
1067
|
const cleanup = cleanupStatus(input.cleanup ?? []);
|
|
1057
1068
|
const replay = input.replay ?? { mode: 'none', status: 'not_requested', artifactIds: [] };
|
|
1058
1069
|
const technicalStatus = input.run.technicalOutcome === 'succeeded' ? 'passed' : input.run.technicalOutcome === 'failed' ? 'failed' : input.run.technicalOutcome === 'pending' ? 'pending' : 'needs_review';
|
|
1059
|
-
const
|
|
1060
|
-
const
|
|
1070
|
+
const semanticNeedsReview = Boolean(input.semanticProposal?.findings.some((finding) => finding.status === 'fail' || finding.status === 'uncertain')) && human?.status !== 'accepted' && human?.status !== 'waived';
|
|
1071
|
+
const status = input.run.technicalOutcome === 'failed' || input.structural?.status === 'failed' ? 'failed' : input.semanticProposal?.status === 'submission_unknown' || semanticNeedsReview || human?.status === 'pending' || human?.status === 'declined' || cleanup.status === 'failed' ? 'needs_review' : input.structural?.status === 'needs_review' ? 'partial' : 'complete';
|
|
1072
|
+
const base = { schemaVersion: EVALUATION_REPORT_SCHEMA_VERSION, id: hashId('evaluation-report', { runId: input.run.id, structural: input.structural?.reportHash, semantic: input.semanticProposal?.reportHash, human: human?.decisionHash }), runId: input.run.id, technicalOutcome: input.run.technicalOutcome, technicalStatus, ...(input.structural === undefined ? {} : { structural: clone(input.structural) }), ...(input.semanticProposal === undefined ? {} : { semanticProposal: clone(input.semanticProposal) }), ...(human === undefined ? {} : { humanAcceptance: human }), cleanup, replay: clone(replay), artifactIds: sortedStrings((input.artifacts ?? []).map((item) => item.id)), sourceHashes: Object.fromEntries(Object.entries(input.sourceHashes ?? {}).sort((left, right) => compareCodeUnits(left[0], right[0]))), status, warnings: sortedStrings([...(input.run.state === 'needs_review' ? ['HUMAN_ACCEPTANCE_REQUIRED'] : []), ...(semanticNeedsReview ? ['SEMANTIC_FINDING_REQUIRES_REVIEW'] : []), ...(cleanup.failureCodes)]) };
|
|
1061
1073
|
return clone({ ...base, reportHash: computeEvaluationReportHash(base) });
|
|
1062
1074
|
}
|
|
1063
1075
|
export const compileEvaluation = compileEvaluationReport;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voce-engine/core",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.4",
|
|
4
4
|
"description": "Deterministic visual ontology, constraint, reference, prompt, execution-planning, and evaluation runtime.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
"dist/m5.d.ts",
|
|
22
22
|
"dist/m6.js",
|
|
23
23
|
"dist/m6.d.ts",
|
|
24
|
+
"dist/composition.js",
|
|
25
|
+
"dist/composition.d.ts",
|
|
24
26
|
"README.md",
|
|
25
27
|
"LICENSE"
|
|
26
28
|
],
|
|
@@ -41,7 +43,7 @@
|
|
|
41
43
|
"tag": "next"
|
|
42
44
|
},
|
|
43
45
|
"dependencies": {
|
|
44
|
-
"@voce-engine/contracts": "0.1.0-rc.
|
|
46
|
+
"@voce-engine/contracts": "0.1.0-rc.4",
|
|
45
47
|
"semver": "7.7.2"
|
|
46
48
|
},
|
|
47
49
|
"devDependencies": {
|