@vertesia/studio-utils 1.5.0-dev.20260910.052912Z → 1.5.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/lib/conditions/index.d.ts +1 -0
- package/lib/conditions/index.d.ts.map +1 -1
- package/lib/conditions/index.js +1 -0
- package/lib/conditions/index.js.map +1 -1
- package/lib/conditions/principal-context.d.ts +55 -0
- package/lib/conditions/principal-context.d.ts.map +1 -0
- package/lib/conditions/principal-context.js +81 -0
- package/lib/conditions/principal-context.js.map +1 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -1
- package/lib/index.js.map +1 -1
- package/lib/prompts/extract-vars.d.ts +44 -12
- package/lib/prompts/extract-vars.d.ts.map +1 -1
- package/lib/prompts/extract-vars.js +165 -75
- package/lib/prompts/extract-vars.js.map +1 -1
- package/lib/prompts/render.d.ts +7 -9
- package/lib/prompts/render.d.ts.map +1 -1
- package/lib/prompts/render.js +12 -15
- package/lib/prompts/render.js.map +1 -1
- package/lib/prompts/validate.d.ts +8 -2
- package/lib/prompts/validate.d.ts.map +1 -1
- package/lib/prompts/validate.js +87 -50
- package/lib/prompts/validate.js.map +1 -1
- package/lib/roles/system.d.ts.map +1 -1
- package/lib/roles/system.js +15 -0
- package/lib/roles/system.js.map +1 -1
- package/lib/vertesia-studio-utils.js +2 -1
- package/lib/vertesia-studio-utils.js.map +1 -1
- package/package.json +6 -5
- package/src/conditions/index.ts +6 -0
- package/src/conditions/principal-context.ts +122 -0
- package/src/index.ts +20 -1
- package/src/prompts/extract-vars.ts +212 -68
- package/src/prompts/render.test.ts +13 -2
- package/src/prompts/render.ts +24 -15
- package/src/prompts/validate.test.ts +115 -0
- package/src/prompts/validate.ts +113 -56
- package/src/roles/index.test.ts +9 -9
- package/src/roles/system.ts +16 -0
package/src/prompts/validate.ts
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import type { JSONObject } from '@llumiverse/common';
|
|
2
2
|
import { type JSONSchema, TemplateType } from '@vertesia/common';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
describeTemplateSystemVariables,
|
|
5
|
+
getFreeVariables,
|
|
6
|
+
isTemplateSystemVariable,
|
|
7
|
+
JST_TEMPLATE_GLOBALS,
|
|
8
|
+
renderJsTemplate,
|
|
9
|
+
TEMPLATE_SYSTEM_VARIABLES,
|
|
10
|
+
withTemplateSystemVariables,
|
|
11
|
+
} from '@vertesia/jst';
|
|
12
|
+
import { analyzeHandlebarsTemplate } from './extract-vars.js';
|
|
5
13
|
import { generateMockData } from './mock-data.js';
|
|
6
14
|
import { executeHandlebars } from './render.js';
|
|
7
15
|
|
|
8
16
|
export type PromptValidationIssueType =
|
|
9
17
|
| 'undeclared_template_variable'
|
|
10
18
|
| 'unused_schema_variable'
|
|
19
|
+
| 'reserved_variable_declared'
|
|
20
|
+
| 'system_variable_property_access'
|
|
21
|
+
| 'helper_used_as_value'
|
|
22
|
+
| 'helper_missing_arguments'
|
|
11
23
|
| 'handlebars_render_error'
|
|
12
24
|
| 'jst_unsafe_construct'
|
|
13
25
|
| 'jst_render_error';
|
|
@@ -43,16 +55,6 @@ export interface PromptValidationInput {
|
|
|
43
55
|
inputSchema?: JSONSchema;
|
|
44
56
|
}
|
|
45
57
|
|
|
46
|
-
// JST's renderJsTemplate auto-adds `_` (helpers object) and runtime injects `Set` and `Array`
|
|
47
|
-
// — treat them as globals so they don't appear as free vars in user templates.
|
|
48
|
-
// Globals always available to JST templates regardless of schema:
|
|
49
|
-
// - `_`, `Array`, `Set`: runtime-injected by `renderJsTemplate` (jst library)
|
|
50
|
-
// - `_model`: runtime-injected by the studio-server executor as `{ ..._model: run.modelId }`
|
|
51
|
-
// (see ExecutionRequest.ts:313 and executor/rendering/template.ts:13)
|
|
52
|
-
// Keeping these in sync with `renderTemplate` in ./render.ts so a JST template that runs in
|
|
53
|
-
// production also passes the validator.
|
|
54
|
-
const JST_AUTO_GLOBALS = ['_', 'Array', 'Set', '_model'];
|
|
55
|
-
|
|
56
58
|
function countSeverities(issues: PromptValidationIssue[]): { error_count: number; warning_count: number } {
|
|
57
59
|
let error_count = 0;
|
|
58
60
|
let warning_count = 0;
|
|
@@ -66,42 +68,110 @@ function countSeverities(issues: PromptValidationIssue[]): { error_count: number
|
|
|
66
68
|
return { error_count, warning_count };
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const
|
|
71
|
+
/** Mock data for the render smoke test: schema-shaped input plus the runtime system values. */
|
|
72
|
+
function buildMockInput(inputSchema: JSONSchema): Record<string, unknown> {
|
|
73
|
+
const mockData = generateMockData(inputSchema);
|
|
74
|
+
const mockObject: JSONObject =
|
|
75
|
+
typeof mockData === 'object' && mockData !== null && !Array.isArray(mockData) ? (mockData as JSONObject) : {};
|
|
76
|
+
return withTemplateSystemVariables(mockObject, { model: 'validation-model' });
|
|
77
|
+
}
|
|
73
78
|
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Issues shared by both template languages: reserved names declared in the schema, and
|
|
81
|
+
* unused schema properties. `usedVars` holds the input variables the template reads.
|
|
82
|
+
*/
|
|
83
|
+
function checkDeclarations(
|
|
84
|
+
declaredVars: Set<string>,
|
|
85
|
+
usedVars: Set<string>,
|
|
86
|
+
usageHint: (name: string) => string,
|
|
87
|
+
): PromptValidationIssue[] {
|
|
88
|
+
const issues: PromptValidationIssue[] = [];
|
|
89
|
+
for (const declared of declaredVars) {
|
|
90
|
+
if (isTemplateSystemVariable(declared)) {
|
|
76
91
|
issues.push({
|
|
77
|
-
type: '
|
|
78
|
-
severity: '
|
|
79
|
-
variable:
|
|
80
|
-
message: `
|
|
92
|
+
type: 'reserved_variable_declared',
|
|
93
|
+
severity: 'warning',
|
|
94
|
+
variable: declared,
|
|
95
|
+
message: `Schema declares '${declared}', a system variable the runtime supplies and overrides. Remove it from input_schema.`,
|
|
81
96
|
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
for (const declared of declaredVars) {
|
|
86
|
-
if (!usedVars.has(declared)) {
|
|
97
|
+
} else if (!usedVars.has(declared)) {
|
|
87
98
|
issues.push({
|
|
88
99
|
type: 'unused_schema_variable',
|
|
89
100
|
severity: 'warning',
|
|
90
101
|
variable: declared,
|
|
91
|
-
message: `Schema declares property '${declared}' but the template never references it. Remove it from input_schema or
|
|
102
|
+
message: `Schema declares property '${declared}' but the template never references it. Remove it from input_schema or ${usageHint(declared)}.`,
|
|
92
103
|
});
|
|
93
104
|
}
|
|
94
105
|
}
|
|
106
|
+
return issues;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function undeclaredVariableIssue(name: string, where: string): PromptValidationIssue {
|
|
110
|
+
return {
|
|
111
|
+
type: 'undeclared_template_variable',
|
|
112
|
+
severity: 'error',
|
|
113
|
+
variable: name,
|
|
114
|
+
message:
|
|
115
|
+
`Template reads variable '${name}' ${where} but it is not declared in input_schema.properties. ` +
|
|
116
|
+
`Add '${name}' to the schema with an appropriate type. ` +
|
|
117
|
+
`System variables need no declaration: ${describeTemplateSystemVariables()}.`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function validateHandlebarsPrompt(content: string, inputSchema?: JSONSchema): PromptValidationIssue[] {
|
|
122
|
+
const issues: PromptValidationIssue[] = [];
|
|
123
|
+
const analysis = analyzeHandlebarsTemplate(content);
|
|
124
|
+
const declaredVars = new Set<string>(inputSchema?.properties ? Object.keys(inputSchema.properties) : []);
|
|
125
|
+
const usedVars = new Set<string>();
|
|
126
|
+
const reported = new Set<string>();
|
|
127
|
+
|
|
128
|
+
for (const ref of analysis?.references ?? []) {
|
|
129
|
+
if (isTemplateSystemVariable(ref.name)) {
|
|
130
|
+
const variable = TEMPLATE_SYSTEM_VARIABLES.find((v) => v.name === ref.name);
|
|
131
|
+
if (ref.hasPath && variable?.type === 'string' && !reported.has(`path:${ref.expression}`)) {
|
|
132
|
+
reported.add(`path:${ref.expression}`);
|
|
133
|
+
issues.push({
|
|
134
|
+
type: 'system_variable_property_access',
|
|
135
|
+
severity: 'error',
|
|
136
|
+
variable: ref.name,
|
|
137
|
+
message: `'${ref.expression}' in ${ref.tag} reads a property of system variable '${ref.name}', which is a string (${variable.description}). Use '${ref.name}' directly.`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
usedVars.add(ref.name);
|
|
143
|
+
if (!declaredVars.has(ref.name) && !reported.has(ref.name)) {
|
|
144
|
+
reported.add(ref.name);
|
|
145
|
+
issues.push(undeclaredVariableIssue(ref.name, `in ${ref.tag}`));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (const misuse of analysis?.helperMisuses ?? []) {
|
|
150
|
+
issues.push(
|
|
151
|
+
misuse.problem === 'used_as_value'
|
|
152
|
+
? {
|
|
153
|
+
type: 'helper_used_as_value',
|
|
154
|
+
severity: 'error',
|
|
155
|
+
variable: misuse.helper,
|
|
156
|
+
message: `'${misuse.helper}' in ${misuse.tag} is a helper, but as an argument Handlebars reads it as data, which is empty. Call it as a subexpression: (${misuse.helper} ...).`,
|
|
157
|
+
}
|
|
158
|
+
: {
|
|
159
|
+
type: 'helper_missing_arguments',
|
|
160
|
+
severity: 'error',
|
|
161
|
+
variable: misuse.helper,
|
|
162
|
+
message: `${misuse.tag} calls helper '${misuse.helper}' without arguments. Pass the value it operates on, e.g. {{${misuse.helper} value}}.`,
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
issues.push(...checkDeclarations(declaredVars, usedVars, (name) => `use it via {{${name}}}`));
|
|
95
168
|
|
|
96
169
|
// Render-time smoke test — always runs so syntax errors and failing helper calls are
|
|
97
170
|
// surfaced even when undeclared-variable errors are already in the list. Handlebars renders
|
|
98
171
|
// missing vars as empty strings (non-strict by default), so the render check does NOT echo
|
|
99
172
|
// the var errors — anything it reports is a distinct template problem worth showing.
|
|
100
173
|
const renderSchema = inputSchema ?? ({} as JSONSchema);
|
|
101
|
-
const
|
|
102
|
-
const mockObject: JSONObject =
|
|
103
|
-
typeof mockData === 'object' && mockData !== null && !Array.isArray(mockData) ? (mockData as JSONObject) : {};
|
|
104
|
-
const renderResult = executeHandlebars(content, renderSchema, mockObject);
|
|
174
|
+
const renderResult = executeHandlebars(content, renderSchema, buildMockInput(renderSchema) as JSONObject);
|
|
105
175
|
if (!renderResult.success) {
|
|
106
176
|
issues.push({
|
|
107
177
|
type: 'handlebars_render_error',
|
|
@@ -120,7 +190,7 @@ function validateJstPrompt(content: string, inputSchema?: JSONSchema): PromptVal
|
|
|
120
190
|
let referenced: Set<string>;
|
|
121
191
|
try {
|
|
122
192
|
const result = getFreeVariables(content, {
|
|
123
|
-
globals:
|
|
193
|
+
globals: [...JST_TEMPLATE_GLOBALS],
|
|
124
194
|
acorn: { allowReturnOutsideFunction: true, locations: true },
|
|
125
195
|
});
|
|
126
196
|
referenced = result.vars;
|
|
@@ -143,38 +213,19 @@ function validateJstPrompt(content: string, inputSchema?: JSONSchema): PromptVal
|
|
|
143
213
|
|
|
144
214
|
for (const used of referenced) {
|
|
145
215
|
if (!declaredVars.has(used)) {
|
|
146
|
-
issues.push(
|
|
147
|
-
type: 'undeclared_template_variable',
|
|
148
|
-
severity: 'error',
|
|
149
|
-
variable: used,
|
|
150
|
-
message: `Template references variable '${used}' but it is not declared in input_schema.properties. Add '${used}' to the schema with an appropriate type.`,
|
|
151
|
-
});
|
|
216
|
+
issues.push(undeclaredVariableIssue(used, 'in the template'));
|
|
152
217
|
}
|
|
153
218
|
}
|
|
154
219
|
|
|
155
|
-
|
|
156
|
-
if (!referenced.has(declared)) {
|
|
157
|
-
issues.push({
|
|
158
|
-
type: 'unused_schema_variable',
|
|
159
|
-
severity: 'warning',
|
|
160
|
-
variable: declared,
|
|
161
|
-
message: `Schema declares property '${declared}' but the template never references it. Remove it from input_schema or use it in the template.`,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
}
|
|
220
|
+
issues.push(...checkDeclarations(declaredVars, referenced, () => 'use it in the template'));
|
|
165
221
|
|
|
166
222
|
// Render-time smoke test — only if there are no blocking errors so far, otherwise
|
|
167
223
|
// the failure mode would just echo what we already reported.
|
|
168
224
|
const blockingSoFar = issues.some((i) => i.severity === 'error');
|
|
169
225
|
if (!blockingSoFar) {
|
|
170
226
|
const renderSchema = inputSchema ?? ({} as JSONSchema);
|
|
171
|
-
const mockData = generateMockData(renderSchema);
|
|
172
|
-
const mockObject: JSONObject =
|
|
173
|
-
typeof mockData === 'object' && mockData !== null && !Array.isArray(mockData)
|
|
174
|
-
? (mockData as JSONObject)
|
|
175
|
-
: {};
|
|
176
227
|
try {
|
|
177
|
-
renderJsTemplate(content, [...declaredVars],
|
|
228
|
+
renderJsTemplate(content, [...declaredVars], buildMockInput(renderSchema));
|
|
178
229
|
} catch (renderError) {
|
|
179
230
|
issues.push({
|
|
180
231
|
type: 'jst_render_error',
|
|
@@ -192,9 +243,15 @@ function validateJstPrompt(content: string, inputSchema?: JSONSchema): PromptVal
|
|
|
192
243
|
*
|
|
193
244
|
* For `handlebars` and `jst` templates, the following checks are performed:
|
|
194
245
|
* 1. Every variable referenced in the template must be declared as a top-level property
|
|
195
|
-
* in `inputSchema.properties` (else → `undeclared_template_variable` error).
|
|
246
|
+
* in `inputSchema.properties` (else → `undeclared_template_variable` error). System variables
|
|
247
|
+
* (`TEMPLATE_SYSTEM_VARIABLES` in `@vertesia/jst`) are supplied at runtime and need no declaration;
|
|
248
|
+
* declaring one → `reserved_variable_declared` warning, reading a property of one →
|
|
249
|
+
* `system_variable_property_access` error.
|
|
196
250
|
* 2. Every property declared in `inputSchema.properties` should be referenced by the template
|
|
197
251
|
* (else → `unused_schema_variable` warning — non-blocking).
|
|
252
|
+
* For Handlebars only: a helper passed as an argument (`{{#if stringify}}`) →
|
|
253
|
+
* `helper_used_as_value` error; a helper called bare without the arguments it needs
|
|
254
|
+
* (`{{stringify}}`) → `helper_missing_arguments` error.
|
|
198
255
|
* 3. The template must render successfully against schema-derived mock data
|
|
199
256
|
* (else → `handlebars_render_error` / `jst_render_error` error).
|
|
200
257
|
* 4. For JST only: unsafe constructs (`with`, `for`, `while`, `import`, class, `this`,
|
package/src/roles/index.test.ts
CHANGED
|
@@ -46,27 +46,27 @@ describe('getRoleByName', () => {
|
|
|
46
46
|
describe('listRoles', () => {
|
|
47
47
|
it('returns every role across all partitions', () => {
|
|
48
48
|
const roles = listRoles();
|
|
49
|
-
//
|
|
50
|
-
expect(roles).toHaveLength(
|
|
49
|
+
// 17 system roles + 3 content roles
|
|
50
|
+
expect(roles).toHaveLength(20);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
it('lists system roles before content roles (partition registration order)', () => {
|
|
54
54
|
const roles = listRoles();
|
|
55
55
|
const systemCount = roles.filter((r) => r.domain === 'system').length;
|
|
56
56
|
const contentCount = roles.filter((r) => r.domain === 'content').length;
|
|
57
|
-
expect(systemCount).toBe(
|
|
57
|
+
expect(systemCount).toBe(17);
|
|
58
58
|
expect(contentCount).toBe(3);
|
|
59
59
|
|
|
60
|
-
// First
|
|
61
|
-
for (let i = 0; i <
|
|
62
|
-
for (let i =
|
|
60
|
+
// First 17 are system, next 3 are content
|
|
61
|
+
for (let i = 0; i < 17; i++) expect(roles[i].domain).toBe('system');
|
|
62
|
+
for (let i = 17; i < 20; i++) expect(roles[i].domain).toBe('content');
|
|
63
63
|
});
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
describe('listRolesByDomain', () => {
|
|
67
67
|
it('returns only system roles for "system"', () => {
|
|
68
68
|
const roles = listRolesByDomain('system');
|
|
69
|
-
expect(roles).toHaveLength(
|
|
69
|
+
expect(roles).toHaveLength(17);
|
|
70
70
|
expect(roles.every((r) => r.domain === 'system')).toBe(true);
|
|
71
71
|
});
|
|
72
72
|
|
|
@@ -84,7 +84,7 @@ describe('listRolesByDomain', () => {
|
|
|
84
84
|
describe('listSystemRoles', () => {
|
|
85
85
|
it('returns SystemRole instances', () => {
|
|
86
86
|
const roles = listSystemRoles();
|
|
87
|
-
expect(roles).toHaveLength(
|
|
87
|
+
expect(roles).toHaveLength(17);
|
|
88
88
|
expect(roles.every((r) => r instanceof SystemRole)).toBe(true);
|
|
89
89
|
});
|
|
90
90
|
|
|
@@ -120,7 +120,7 @@ describe('listAbacRolesForScope', () => {
|
|
|
120
120
|
describe('getAllRoleNames', () => {
|
|
121
121
|
it('returns names of every registered role', () => {
|
|
122
122
|
const names = getAllRoleNames();
|
|
123
|
-
expect(names).toHaveLength(
|
|
123
|
+
expect(names).toHaveLength(20);
|
|
124
124
|
expect(names).toContain('owner');
|
|
125
125
|
expect(names).toContain('content:reader');
|
|
126
126
|
});
|
package/src/roles/system.ts
CHANGED
|
@@ -19,6 +19,21 @@ class AdminRole extends OrgMemberRole {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Full `admin` rights minus Studio UI access. For an account administrator who operates the
|
|
24
|
+
* platform through a custom admin UI and must not reach Vertesia Studio itself.
|
|
25
|
+
*
|
|
26
|
+
* Keep in lockstep with `AdminRole`: `studio_access` is the only intended difference, so a new
|
|
27
|
+
* permission added to the central enum reaches both roles automatically.
|
|
28
|
+
*/
|
|
29
|
+
class AppAdminRole extends AdminRole {
|
|
30
|
+
constructor() {
|
|
31
|
+
super();
|
|
32
|
+
this.name = SystemRoles.app_admin;
|
|
33
|
+
this.permissions.delete(Permission.studio_access);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
22
37
|
class ManagerRole extends OrgMemberRole {
|
|
23
38
|
constructor() {
|
|
24
39
|
super(SystemRoles.manager, Object.values(Permission));
|
|
@@ -182,6 +197,7 @@ class ContentSuperAdmin extends DeveloperRole {
|
|
|
182
197
|
const systemRoles: Record<SystemRoles, Role> = {
|
|
183
198
|
[SystemRoles.owner]: new OwnerRole(),
|
|
184
199
|
[SystemRoles.admin]: new AdminRole(),
|
|
200
|
+
[SystemRoles.app_admin]: new AppAdminRole(),
|
|
185
201
|
[SystemRoles.manager]: new ManagerRole(),
|
|
186
202
|
[SystemRoles.developer]: new DeveloperRole(),
|
|
187
203
|
[SystemRoles.application]: new ApplicationRole(),
|