@pikku/inspector 0.12.50 → 0.12.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +743 -0
- package/OPTIMIZATION-PLAN.md +9 -3
- package/dist/add/add-auth.d.ts +1 -1
- package/dist/add/add-auth.js +1 -1
- package/dist/add/add-cli.js +7 -0
- package/dist/add/add-credential.js +1 -1
- package/dist/add/add-functions.js +199 -8
- package/dist/add/add-http-route.js +16 -6
- package/dist/add/add-middleware.js +46 -3
- package/dist/add/add-personas.d.ts +13 -0
- package/dist/add/add-personas.js +216 -0
- package/dist/add/add-scope.js +1 -1
- package/dist/add/add-secret.js +1 -1
- package/dist/add/add-system-role.d.ts +2 -0
- package/dist/add/add-system-role.js +102 -0
- package/dist/add/add-variable.js +1 -1
- package/dist/add/add-wire-addon.js +31 -0
- package/dist/add/add-workflow.js +12 -1
- package/dist/error-codes.d.ts +17 -1
- package/dist/error-codes.js +23 -0
- package/dist/index.d.ts +1 -1
- package/dist/inspector.js +33 -1
- package/dist/types.d.ts +55 -2
- package/dist/utils/annotate-http-route-auth.d.ts +22 -0
- package/dist/utils/annotate-http-route-auth.js +48 -0
- package/dist/utils/check-secret-sinks.d.ts +31 -0
- package/dist/utils/check-secret-sinks.js +117 -0
- package/dist/utils/extract-function-name.js +13 -4
- package/dist/utils/extract-function-node.js +18 -1
- package/dist/utils/extract-secret-usage.d.ts +9 -0
- package/dist/utils/extract-secret-usage.js +61 -0
- package/dist/utils/literal-properties.d.ts +22 -0
- package/dist/utils/literal-properties.js +69 -0
- package/dist/utils/load-addon-functions-meta.js +26 -1
- package/dist/utils/permissions.js +8 -0
- package/dist/utils/post-process.d.ts +68 -3
- package/dist/utils/post-process.js +246 -13
- package/dist/utils/secret-alias-services.d.ts +8 -0
- package/dist/utils/secret-alias-services.js +45 -0
- package/dist/utils/secret-brokers.d.ts +2 -0
- package/dist/utils/secret-brokers.js +9 -0
- package/dist/utils/serialize-inspector-state.d.ts +10 -0
- package/dist/utils/serialize-inspector-state.js +18 -0
- package/dist/utils/serialize-middleware-groups-meta.js +3 -0
- package/dist/utils/single-declaration.d.ts +32 -0
- package/dist/utils/single-declaration.js +43 -0
- package/dist/utils/validate-exposed-functions-gated.d.ts +14 -0
- package/dist/utils/validate-exposed-functions-gated.js +93 -0
- package/dist/utils/validate-tags-resolve-to-middleware.d.ts +20 -0
- package/dist/utils/validate-tags-resolve-to-middleware.js +41 -0
- package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +6 -0
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +48 -1
- package/dist/utils/workflow/dsl/patterns.d.ts +9 -4
- package/dist/utils/workflow/dsl/patterns.js +26 -4
- package/dist/visit.js +4 -0
- package/package.json +3 -3
- package/src/add/add-auth.test.ts +5 -3
- package/src/add/add-auth.ts +1 -1
- package/src/add/add-cli.ts +8 -0
- package/src/add/add-credential.ts +1 -1
- package/src/add/add-functions.test.ts +263 -1
- package/src/add/add-functions.ts +273 -11
- package/src/add/add-http-route.ts +20 -7
- package/src/add/add-middleware.test.ts +196 -0
- package/src/add/add-middleware.ts +64 -4
- package/src/add/add-personas.test.ts +382 -0
- package/src/add/add-personas.ts +285 -0
- package/src/add/add-scenario-step.test.ts +87 -5
- package/src/add/add-scope.test.ts +103 -30
- package/src/add/add-scope.ts +1 -1
- package/src/add/add-secret.ts +1 -1
- package/src/add/add-subject-scenario-steps.test.ts +161 -0
- package/src/add/add-system-role.test.ts +297 -0
- package/src/add/add-system-role.ts +164 -0
- package/src/add/add-variable.ts +1 -1
- package/src/add/add-wire-addon.test.ts +111 -0
- package/src/add/add-wire-addon.ts +29 -0
- package/src/add/add-workflow.ts +21 -1
- package/src/add/addon-bans.test.ts +4 -4
- package/src/add/allow-escape-hatches.test.ts +144 -0
- package/src/add/audit-meta.test.ts +75 -0
- package/src/add/config-value-metadata.test.ts +11 -11
- package/src/add/secret-sink-check.test.ts +211 -0
- package/src/add/wire-name-literal.test.ts +2 -2
- package/src/error-codes.ts +26 -0
- package/src/index.ts +4 -1
- package/src/inspector.ts +39 -0
- package/src/types.ts +56 -2
- package/src/utils/annotate-http-route-auth.test.ts +163 -0
- package/src/utils/annotate-http-route-auth.ts +52 -0
- package/src/utils/check-secret-sinks.ts +145 -0
- package/src/utils/extract-function-name.ts +14 -4
- package/src/utils/extract-function-node.ts +18 -6
- package/src/utils/extract-secret-usage.test.ts +81 -0
- package/src/utils/extract-secret-usage.ts +73 -0
- package/src/utils/literal-properties.ts +97 -0
- package/src/utils/load-addon-functions-meta.ts +32 -2
- package/src/utils/permissions-shorthand.test.ts +79 -0
- package/src/utils/permissions.ts +7 -0
- package/src/utils/post-process.test.ts +133 -0
- package/src/utils/post-process.ts +313 -12
- package/src/utils/secret-alias-services.test.ts +146 -0
- package/src/utils/secret-alias-services.ts +58 -0
- package/src/utils/secret-brokers.test.ts +53 -0
- package/src/utils/secret-brokers.ts +11 -0
- package/src/utils/serialize-inspector-state.ts +28 -0
- package/src/utils/serialize-middleware-groups-meta.ts +3 -0
- package/src/utils/single-declaration.ts +57 -0
- package/src/utils/validate-exposed-functions-gated.test.ts +230 -0
- package/src/utils/validate-exposed-functions-gated.ts +103 -0
- package/src/utils/validate-tags-resolve-to-middleware.test.ts +105 -0
- package/src/utils/validate-tags-resolve-to-middleware.ts +48 -0
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +57 -0
- package/src/utils/workflow/dsl/patterns.ts +33 -4
- package/src/visit.ts +4 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { DISPOSITIONS } from '@pikku/core/virtual-user';
|
|
3
|
+
import { isRunnablePersona } from '@pikku/core/persona';
|
|
4
|
+
import { ErrorCode } from '../error-codes.js';
|
|
5
|
+
import { claimSingleDeclaration } from '../utils/single-declaration.js';
|
|
6
|
+
import { booleanProperty, getProperty, numberProperty, stringArrayProperty, stringProperty, unwrapAs, } from '../utils/literal-properties.js';
|
|
7
|
+
/** A dial and the range outside which it is a mistake rather than a choice. */
|
|
8
|
+
const RATES = [
|
|
9
|
+
['temperature', 0, 2],
|
|
10
|
+
['repeatRate', 0, 1],
|
|
11
|
+
['reReadRate', 0, 1],
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* `{ moves, temperature, repeatRate, … }` — a disposition's dials, overridden.
|
|
15
|
+
*
|
|
16
|
+
* The ranges are checked here rather than at run time because these are the
|
|
17
|
+
* kind of typo that does not throw: `repeatRate: 18` meaning 18% would silently
|
|
18
|
+
* double every call the persona makes, and you would read the run as a product
|
|
19
|
+
* bug.
|
|
20
|
+
*/
|
|
21
|
+
const tuningProperty = (config, onError) => {
|
|
22
|
+
const value = getProperty(config, 'tuning');
|
|
23
|
+
if (!value || !ts.isObjectLiteralExpression(value)) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const tuning = {};
|
|
27
|
+
const movesValue = getProperty(value, 'moves');
|
|
28
|
+
if (movesValue && ts.isObjectLiteralExpression(movesValue)) {
|
|
29
|
+
const moves = {};
|
|
30
|
+
for (const move of ['continue', 'suspend', 'resume', 'abandon']) {
|
|
31
|
+
const weight = numberProperty(movesValue, move);
|
|
32
|
+
if (weight === undefined) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (weight < 0) {
|
|
36
|
+
onError(`tuning.moves.${move} must not be negative (got ${weight}).`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
moves[move] = weight;
|
|
40
|
+
}
|
|
41
|
+
if (Object.keys(moves).length) {
|
|
42
|
+
tuning.moves = moves;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (const [name, min, max] of RATES) {
|
|
46
|
+
const rate = numberProperty(value, name);
|
|
47
|
+
if (rate === undefined) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (rate < min || rate > max) {
|
|
51
|
+
onError(`tuning.${name} must be between ${min} and ${max} (got ${rate}).`);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
Object.assign(tuning, { [name]: rate });
|
|
55
|
+
}
|
|
56
|
+
for (const flag of ['emptyMemory', 'readOnly', 'invertedOracle']) {
|
|
57
|
+
const declared = booleanProperty(value, flag);
|
|
58
|
+
if (declared !== undefined) {
|
|
59
|
+
tuning[flag] = declared;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const instructions = stringProperty(value, 'instructions');
|
|
63
|
+
if (instructions !== undefined) {
|
|
64
|
+
tuning.instructions = instructions;
|
|
65
|
+
}
|
|
66
|
+
return Object.keys(tuning).length ? tuning : undefined;
|
|
67
|
+
};
|
|
68
|
+
const accountProperty = (value) => {
|
|
69
|
+
if (!value || !ts.isObjectLiteralExpression(value)) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const provider = stringProperty(value, 'provider');
|
|
73
|
+
return provider === undefined ? {} : { provider };
|
|
74
|
+
};
|
|
75
|
+
const linkedAccountsProperty = (config, personaId, logger) => {
|
|
76
|
+
const value = getProperty(config, 'linkedAccounts');
|
|
77
|
+
if (!value || !ts.isObjectLiteralExpression(value)) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const accounts = {};
|
|
81
|
+
for (const prop of value.properties) {
|
|
82
|
+
if (!ts.isPropertyAssignment(prop)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name)) {
|
|
86
|
+
logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, `Persona '${personaId}' has a linked account whose key is not a literal.`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const account = accountProperty(prop.initializer);
|
|
90
|
+
if (!account) {
|
|
91
|
+
logger.critical(ErrorCode.INVALID_VALUE, `Persona '${personaId}' linked account '${prop.name.text}' must be an object literal.`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
accounts[prop.name.text] = account;
|
|
95
|
+
}
|
|
96
|
+
return Object.keys(accounts).length ? accounts : undefined;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Inspector for `definePersonas()` calls.
|
|
100
|
+
*
|
|
101
|
+
* A persona is entirely declaration — a person, their roles, what they want —
|
|
102
|
+
* so there is no code body and nothing to resolve at runtime. What is read here
|
|
103
|
+
* is the whole thing, and it is generated straight to `personas.gen.json` for
|
|
104
|
+
* the CLI and the console to read without loading the app.
|
|
105
|
+
*
|
|
106
|
+
* `name` is the one field that cannot be defaulted. A persona without one is
|
|
107
|
+
* not a person, and the console has nothing to show.
|
|
108
|
+
*/
|
|
109
|
+
export const addPersonas = (logger, node, _checker, state) => {
|
|
110
|
+
if (!ts.isCallExpression(node)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const expression = node.expression;
|
|
114
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'definePersonas') {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const firstArg = node.arguments[0];
|
|
118
|
+
if (!firstArg) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const unwrapped = unwrapAs(firstArg);
|
|
122
|
+
if (!ts.isObjectLiteralExpression(unwrapped)) {
|
|
123
|
+
logger.critical(ErrorCode.INVALID_VALUE, 'definePersonas() needs an inline object — personas are read statically, never evaluated.');
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const sourceFile = node.getSourceFile().fileName;
|
|
127
|
+
if (!claimSingleDeclaration(logger, state.personas.files, ErrorCode.DUPLICATE_PERSONAS_DEFINITION, 'definePersonas', sourceFile)) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const known = Object.keys(DISPOSITIONS);
|
|
131
|
+
for (const prop of unwrapped.properties) {
|
|
132
|
+
if (!ts.isPropertyAssignment(prop)) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name)) {
|
|
136
|
+
logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, 'A persona is declared with a key that is not a literal.');
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const id = prop.name.text;
|
|
140
|
+
if (!ts.isObjectLiteralExpression(prop.initializer)) {
|
|
141
|
+
logger.critical(ErrorCode.INVALID_VALUE, `Persona '${id}' must be an object literal.`);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const config = prop.initializer;
|
|
145
|
+
const name = stringProperty(config, 'name');
|
|
146
|
+
if (!name) {
|
|
147
|
+
logger.critical(ErrorCode.INVALID_VALUE, `Persona '${id}' needs a literal 'name' — a persona is a person, and the console has nothing to show without one.`);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
// The generated `definePersonas` types this as a union, so a bad value only
|
|
151
|
+
// reaches here in source that does not compile. Caught anyway: a
|
|
152
|
+
// disposition is the entire behaviour, and defaulting a typo to `realistic`
|
|
153
|
+
// would run a different person than the one that was written.
|
|
154
|
+
const disposition = stringProperty(config, 'disposition');
|
|
155
|
+
if (disposition !== undefined && !known.includes(disposition)) {
|
|
156
|
+
logger.critical(ErrorCode.INVALID_VALUE, `Persona '${id}' has an unknown disposition '${disposition}'. One of: ${known.join(', ')}`);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
let tuningFailed = false;
|
|
160
|
+
const tuning = tuningProperty(config, (message) => {
|
|
161
|
+
tuningFailed = true;
|
|
162
|
+
logger.critical(ErrorCode.INVALID_VALUE, `Persona '${id}': ${message}`);
|
|
163
|
+
});
|
|
164
|
+
if (tuningFailed) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const account = accountProperty(getProperty(config, 'account'));
|
|
168
|
+
const declaredRunnable = booleanProperty(config, 'runnable');
|
|
169
|
+
const persona = {
|
|
170
|
+
id,
|
|
171
|
+
name,
|
|
172
|
+
roles: stringArrayProperty(config, 'roles') ?? [],
|
|
173
|
+
goals: stringArrayProperty(config, 'goals') ?? [],
|
|
174
|
+
tags: stringArrayProperty(config, 'tags') ?? [],
|
|
175
|
+
runnable: isRunnablePersona({
|
|
176
|
+
...(declaredRunnable !== undefined
|
|
177
|
+
? { runnable: declaredRunnable }
|
|
178
|
+
: {}),
|
|
179
|
+
...(account ? { account } : {}),
|
|
180
|
+
}),
|
|
181
|
+
sourceFile,
|
|
182
|
+
};
|
|
183
|
+
const jobTitle = stringProperty(config, 'jobTitle');
|
|
184
|
+
if (jobTitle !== undefined)
|
|
185
|
+
persona.jobTitle = jobTitle;
|
|
186
|
+
const description = stringProperty(config, 'description');
|
|
187
|
+
if (description !== undefined)
|
|
188
|
+
persona.description = description;
|
|
189
|
+
const avatarUrl = stringProperty(config, 'avatarUrl');
|
|
190
|
+
if (avatarUrl !== undefined)
|
|
191
|
+
persona.avatarUrl = avatarUrl;
|
|
192
|
+
const personality = stringProperty(config, 'personality');
|
|
193
|
+
if (personality !== undefined)
|
|
194
|
+
persona.personality = personality;
|
|
195
|
+
if (disposition !== undefined) {
|
|
196
|
+
persona.disposition = disposition;
|
|
197
|
+
}
|
|
198
|
+
if (tuning !== undefined)
|
|
199
|
+
persona.tuning = tuning;
|
|
200
|
+
const fixtures = stringArrayProperty(config, 'fixtures');
|
|
201
|
+
if (fixtures !== undefined)
|
|
202
|
+
persona.fixtures = fixtures;
|
|
203
|
+
// Left absent rather than resolved here: "everywhere but production" is a
|
|
204
|
+
// fact about the config, and the inspector has no business baking today's
|
|
205
|
+
// environment list into a meta file that outlives it.
|
|
206
|
+
const environments = stringArrayProperty(config, 'environments');
|
|
207
|
+
if (environments !== undefined)
|
|
208
|
+
persona.environments = environments;
|
|
209
|
+
if (account !== undefined)
|
|
210
|
+
persona.account = account;
|
|
211
|
+
const linkedAccounts = linkedAccountsProperty(config, id, logger);
|
|
212
|
+
if (linkedAccounts !== undefined)
|
|
213
|
+
persona.linkedAccounts = linkedAccounts;
|
|
214
|
+
state.personas.definitions.push(persona);
|
|
215
|
+
}
|
|
216
|
+
};
|
package/dist/add/add-scope.js
CHANGED
|
@@ -89,7 +89,7 @@ export const addScope = (logger, node, checker, state, _options) => {
|
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
91
91
|
const expression = node.expression;
|
|
92
|
-
if (!ts.isIdentifier(expression) || expression.text !== '
|
|
92
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'defineScope') {
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
95
|
const firstArg = node.arguments[0];
|
package/dist/add/add-secret.js
CHANGED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
3
|
+
import { ErrorCode } from '../error-codes.js';
|
|
4
|
+
const SEPARATOR = ':';
|
|
5
|
+
/**
|
|
6
|
+
* Unwraps `x as const` / `x satisfies T` so a cast declaration is still
|
|
7
|
+
* extracted rather than silently skipped.
|
|
8
|
+
*/
|
|
9
|
+
const unwrapAs = (node) => ts.isAsExpression(node) || ts.isSatisfiesExpression(node)
|
|
10
|
+
? unwrapAs(node.expression)
|
|
11
|
+
: node;
|
|
12
|
+
/**
|
|
13
|
+
* Reads a `scopes: [...]` array of string literals.
|
|
14
|
+
*
|
|
15
|
+
* Returns `undefined` rather than an empty array when the property is absent
|
|
16
|
+
* or unreadable, so the caller can tell "declared no scopes" from "we could
|
|
17
|
+
* not see the scopes" — the first is a legitimate placeholder role, the second
|
|
18
|
+
* would silently produce one.
|
|
19
|
+
*/
|
|
20
|
+
const extractScopes = (obj, roleName, logger) => {
|
|
21
|
+
const prop = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
22
|
+
(ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
|
|
23
|
+
p.name.text === 'scopes');
|
|
24
|
+
if (!prop) {
|
|
25
|
+
logger.critical(ErrorCode.INVALID_VALUE, `System role '${roleName}' has no 'scopes'. A role that grants nothing should say so with 'scopes: []'.`);
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
if (!ts.isPropertyAssignment(prop) ||
|
|
29
|
+
!ts.isArrayLiteralExpression(prop.initializer)) {
|
|
30
|
+
logger.critical(ErrorCode.INVALID_VALUE, `System role '${roleName}' must declare 'scopes' as an array literal.`);
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const scopes = [];
|
|
34
|
+
for (const element of prop.initializer.elements) {
|
|
35
|
+
const unwrapped = unwrapAs(element);
|
|
36
|
+
if (!ts.isStringLiteral(unwrapped)) {
|
|
37
|
+
logger.critical(ErrorCode.INVALID_VALUE, `System role '${roleName}' lists a scope that is not a string literal. ` +
|
|
38
|
+
`Scopes are extracted by AST, so a computed value cannot be checked against the declared set.`);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
scopes.push(unwrapped.text);
|
|
42
|
+
}
|
|
43
|
+
return scopes;
|
|
44
|
+
};
|
|
45
|
+
export const addSystemRole = (logger, node, _checker, state, _options) => {
|
|
46
|
+
if (!ts.isCallExpression(node)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const expression = node.expression;
|
|
50
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'defineSystemRole') {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const firstArg = node.arguments[0];
|
|
54
|
+
if (!firstArg) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const unwrapped = unwrapAs(firstArg);
|
|
58
|
+
if (!ts.isObjectLiteralExpression(unwrapped)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const sourceFile = node.getSourceFile().fileName;
|
|
62
|
+
for (const prop of unwrapped.properties) {
|
|
63
|
+
if (!ts.isPropertyAssignment(prop)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
let name;
|
|
67
|
+
if (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) {
|
|
68
|
+
name = prop.name.text;
|
|
69
|
+
}
|
|
70
|
+
if (name === undefined) {
|
|
71
|
+
logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, 'A system role is declared with a key that is not a literal.');
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (name.length === 0) {
|
|
75
|
+
logger.critical(ErrorCode.INVALID_VALUE, 'A system role is declared with an empty name.');
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (name.includes(SEPARATOR)) {
|
|
79
|
+
logger.critical(ErrorCode.INVALID_VALUE, `System role '${name}' contains the '${SEPARATOR}' separator. ` +
|
|
80
|
+
`'${SEPARATOR}' delimits scope ids; a role named like a scope reads as one.`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (!ts.isObjectLiteralExpression(prop.initializer)) {
|
|
84
|
+
logger.critical(ErrorCode.INVALID_VALUE, `System role '${name}' must be an object literal.`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const scopes = extractScopes(prop.initializer, name, logger);
|
|
88
|
+
if (scopes === undefined) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const displayName = getPropertyValue(prop.initializer, 'displayName');
|
|
92
|
+
const description = getPropertyValue(prop.initializer, 'description');
|
|
93
|
+
state.systemRoles.files.add(sourceFile);
|
|
94
|
+
state.systemRoles.definitions.push({
|
|
95
|
+
name,
|
|
96
|
+
displayName: displayName || undefined,
|
|
97
|
+
description: description || undefined,
|
|
98
|
+
scopes,
|
|
99
|
+
sourceFile,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
};
|
package/dist/add/add-variable.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
+
function parseStringArray(node) {
|
|
3
|
+
if (!ts.isArrayLiteralExpression(node))
|
|
4
|
+
return undefined;
|
|
5
|
+
const values = [];
|
|
6
|
+
for (const element of node.elements) {
|
|
7
|
+
// A non-literal entry (a spread, a const reference) is not statically
|
|
8
|
+
// knowable. Dropping the whole array rather than a silent partial keeps a
|
|
9
|
+
// consumer from reading a short list as the complete set of gates.
|
|
10
|
+
if (!ts.isStringLiteral(element))
|
|
11
|
+
return undefined;
|
|
12
|
+
values.push(element.text);
|
|
13
|
+
}
|
|
14
|
+
return values;
|
|
15
|
+
}
|
|
2
16
|
function parseStringRecord(obj) {
|
|
3
17
|
const result = {};
|
|
4
18
|
for (const prop of obj.properties) {
|
|
@@ -33,6 +47,9 @@ export function addWireAddon(node, state, logger) {
|
|
|
33
47
|
let pkg;
|
|
34
48
|
let rpcEndpoint;
|
|
35
49
|
let mcp;
|
|
50
|
+
let auth;
|
|
51
|
+
let tags;
|
|
52
|
+
let scopes;
|
|
36
53
|
let secretOverrides;
|
|
37
54
|
let variableOverrides;
|
|
38
55
|
let credentialOverrides;
|
|
@@ -54,6 +71,17 @@ export function addWireAddon(node, state, logger) {
|
|
|
54
71
|
prop.initializer.kind === ts.SyntaxKind.FalseKeyword)) {
|
|
55
72
|
mcp = prop.initializer.kind === ts.SyntaxKind.TrueKeyword;
|
|
56
73
|
}
|
|
74
|
+
else if (key === 'auth' &&
|
|
75
|
+
(prop.initializer.kind === ts.SyntaxKind.TrueKeyword ||
|
|
76
|
+
prop.initializer.kind === ts.SyntaxKind.FalseKeyword)) {
|
|
77
|
+
auth = prop.initializer.kind === ts.SyntaxKind.TrueKeyword;
|
|
78
|
+
}
|
|
79
|
+
else if (key === 'tags') {
|
|
80
|
+
tags = parseStringArray(prop.initializer);
|
|
81
|
+
}
|
|
82
|
+
else if (key === 'scopes') {
|
|
83
|
+
scopes = parseStringArray(prop.initializer);
|
|
84
|
+
}
|
|
57
85
|
else if (key === 'secretOverrides' &&
|
|
58
86
|
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
59
87
|
secretOverrides = parseStringRecord(prop.initializer);
|
|
@@ -74,6 +102,9 @@ export function addWireAddon(node, state, logger) {
|
|
|
74
102
|
package: pkg,
|
|
75
103
|
rpcEndpoint,
|
|
76
104
|
mcp,
|
|
105
|
+
auth,
|
|
106
|
+
tags,
|
|
107
|
+
scopes,
|
|
77
108
|
secretOverrides,
|
|
78
109
|
variableOverrides,
|
|
79
110
|
credentialOverrides,
|
package/dist/add/add-workflow.js
CHANGED
|
@@ -208,7 +208,7 @@ function getWorkflowInvocations(node, checker, state, workflowName, steps) {
|
|
|
208
208
|
* Inspector for pikkuWorkflowFunc(), pikkuWorkflowComplexFunc() and
|
|
209
209
|
* pikkuScenario() calls. Detects workflow registration and extracts metadata.
|
|
210
210
|
*/
|
|
211
|
-
export const addWorkflow = (logger, node, checker, state) => {
|
|
211
|
+
export const addWorkflow = (logger, node, checker, state, options) => {
|
|
212
212
|
if (!ts.isCallExpression(node)) {
|
|
213
213
|
return;
|
|
214
214
|
}
|
|
@@ -223,6 +223,14 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
223
223
|
wrapperType = 'dsl';
|
|
224
224
|
}
|
|
225
225
|
else if (expression.text === 'pikkuWorkflowComplexFunc') {
|
|
226
|
+
if (!options.allow?.complexWorkflows) {
|
|
227
|
+
logger.critical(ErrorCode.COMPLEX_WORKFLOW_NOT_ALLOWED, `'${expression.text}' is used here, but this project has not enabled complex workflows. ` +
|
|
228
|
+
`Their inline steps cannot be serialized, so they are absent from the workflow graph and ` +
|
|
229
|
+
`cannot be replayed or migrated across a definition change. Express the workflow with ` +
|
|
230
|
+
`pikkuWorkflowFunc and the step DSL, or set \`allow: { complexWorkflows: true }\` in ` +
|
|
231
|
+
`pikku.config.json if the project accepts that trade.`);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
226
234
|
wrapperType = 'complex';
|
|
227
235
|
}
|
|
228
236
|
else if (expression.text === 'pikkuScenario') {
|
|
@@ -292,6 +300,7 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
292
300
|
let steps = [];
|
|
293
301
|
let context = undefined;
|
|
294
302
|
let dsl = undefined;
|
|
303
|
+
let asserts = undefined;
|
|
295
304
|
// Try DSL workflow extraction first
|
|
296
305
|
// Pass the whole CallExpression node so findWorkflowFunction can find the arrow function
|
|
297
306
|
const result = extractDSLWorkflow(node, checker, {
|
|
@@ -301,6 +310,7 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
301
310
|
// Extraction succeeded
|
|
302
311
|
steps = result.steps;
|
|
303
312
|
context = result.context;
|
|
313
|
+
asserts = result.asserts;
|
|
304
314
|
// Check if workflow contains inline steps (non-serializable)
|
|
305
315
|
if (hasInlineSteps(steps)) {
|
|
306
316
|
if (wrapperType === 'dsl') {
|
|
@@ -374,6 +384,7 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
374
384
|
expose,
|
|
375
385
|
skip,
|
|
376
386
|
scenario: wrapperType === 'scenario' ? true : undefined,
|
|
387
|
+
asserts,
|
|
377
388
|
actors: actorNames.length > 0 ? actorNames : undefined,
|
|
378
389
|
};
|
|
379
390
|
// The scenario "logger/config only" rule (PKU673) and the implicit
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -23,22 +23,31 @@ export declare enum ErrorCode {
|
|
|
23
23
|
SCENARIO_BROWSER_STEP_NEEDS_ACTOR = "PKU677",
|
|
24
24
|
SCENARIO_STEP_TARGET_NOT_STATIC = "PKU678",
|
|
25
25
|
SCENARIO_NOT_EXTRACTABLE = "PKU679",
|
|
26
|
+
SCENARIO_HAS_NO_ASSERTION = "PKU680",
|
|
26
27
|
WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED = "PKU600",
|
|
27
28
|
INVALID_DSL_WORKFLOW = "PKU641",
|
|
28
29
|
WORKFLOW_GRAPH_ADDON_NOT_WIRED = "PKU642",
|
|
30
|
+
COMPLEX_WORKFLOW_NOT_ALLOWED = "PKU643",
|
|
29
31
|
REMOTE_ADDON_NOT_DEV_DEPENDENCY = "PKU338",
|
|
30
32
|
REMOTE_ADDON_AUTH_UNRESOLVED = "PKU339",
|
|
31
33
|
DB_COLUMN_NAME_TYPE_CONTRADICTION = "PKU480",
|
|
32
34
|
DB_JSON_COLUMN_UNTYPED = "PKU481",
|
|
33
35
|
DB_FORMAT_ON_NON_STRING = "PKU482",
|
|
36
|
+
DB_SECRET_COLUMN_STORED_PLAIN = "PKU483",
|
|
37
|
+
DB_FORM_ON_NON_STRING = "PKU484",
|
|
38
|
+
DB_DEFAULT_SCHEMA_NAME_COLLISION = "PKU485",
|
|
34
39
|
SCHEMA_GENERATION_ERROR = "PKU456",
|
|
35
40
|
INLINE_SCHEMA = "PKU489",
|
|
36
41
|
FUNCTION_METADATA_NOT_FOUND = "PKU559",
|
|
37
42
|
DUPLICATE_AUTH_DEFINITION = "PKU581",
|
|
38
43
|
AUTH_NOT_EXPORTED = "PKU582",
|
|
44
|
+
DUPLICATE_PERSONAS_DEFINITION = "PKU585",
|
|
39
45
|
ROUTE_PARAM_MISMATCH = "PKU571",
|
|
40
46
|
ROUTE_QUERY_MISMATCH = "PKU572",
|
|
41
47
|
AUTH_DISABLED_REQUIRES_SESSIONLESS = "PKU573",
|
|
48
|
+
EXPOSED_FUNCTION_HAS_NO_GATE = "PKU574",
|
|
49
|
+
TAG_RESOLVES_TO_NO_MIDDLEWARE = "PKU575",
|
|
50
|
+
PERMISSIONS_IN_BODY_NOT_ALLOWED = "PKU576",
|
|
42
51
|
DUPLICATE_FUNCTION_VERSION = "PKU850",
|
|
43
52
|
DUPLICATE_FUNCTION_NAME = "PKU851",
|
|
44
53
|
MANIFEST_MISSING = "PKU860",
|
|
@@ -49,6 +58,9 @@ export declare enum ErrorCode {
|
|
|
49
58
|
MANIFEST_INTEGRITY_ERROR = "PKU865",
|
|
50
59
|
MISSING_MODEL = "PKU145",
|
|
51
60
|
INVALID_MODEL = "PKU146",
|
|
61
|
+
AGENT_TOOL_UNKNOWN_NAMESPACE = "PKU152",
|
|
62
|
+
AGENT_TOOL_NOT_FOUND = "PKU153",
|
|
63
|
+
AGENT_TOOL_MISSING_DESCRIPTION = "PKU154",
|
|
52
64
|
SCHEMA_AND_WIRING_COLOCATED = "PKU490",
|
|
53
65
|
SERVICES_NOT_DESTRUCTURED = "PKU410",
|
|
54
66
|
WIRES_NOT_DESTRUCTURED = "PKU411",
|
|
@@ -58,7 +70,11 @@ export declare enum ErrorCode {
|
|
|
58
70
|
PII_IN_OUTPUT = "PKU910",
|
|
59
71
|
ADDON_WIRING_NOT_ALLOWED = "PKU920",
|
|
60
72
|
ADDON_CONTRACT_HANDLERS_NOT_ALLOWED = "PKU921",
|
|
61
|
-
RPC_INVOCATION_TYPE_CAST = "PKU940"
|
|
73
|
+
RPC_INVOCATION_TYPE_CAST = "PKU940",
|
|
74
|
+
SECRET_SERVICE_ALIASED = "PKU950",
|
|
75
|
+
SECRET_NOT_DECLARED = "PKU951",
|
|
76
|
+
SECRET_KEY_NOT_STATIC = "PKU952",
|
|
77
|
+
SECRET_REVEALED_INTO_SINK = "PKU953"
|
|
62
78
|
}
|
|
63
79
|
/**
|
|
64
80
|
* Severity of a tracked, coded diagnostic. `critical` always blocks the build;
|
package/dist/error-codes.js
CHANGED
|
@@ -25,9 +25,11 @@ export var ErrorCode;
|
|
|
25
25
|
ErrorCode["SCENARIO_BROWSER_STEP_NEEDS_ACTOR"] = "PKU677";
|
|
26
26
|
ErrorCode["SCENARIO_STEP_TARGET_NOT_STATIC"] = "PKU678";
|
|
27
27
|
ErrorCode["SCENARIO_NOT_EXTRACTABLE"] = "PKU679";
|
|
28
|
+
ErrorCode["SCENARIO_HAS_NO_ASSERTION"] = "PKU680";
|
|
28
29
|
ErrorCode["WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED"] = "PKU600";
|
|
29
30
|
ErrorCode["INVALID_DSL_WORKFLOW"] = "PKU641";
|
|
30
31
|
ErrorCode["WORKFLOW_GRAPH_ADDON_NOT_WIRED"] = "PKU642";
|
|
32
|
+
ErrorCode["COMPLEX_WORKFLOW_NOT_ALLOWED"] = "PKU643";
|
|
31
33
|
// Remote addon (wireRemoteAddon) validation
|
|
32
34
|
ErrorCode["REMOTE_ADDON_NOT_DEV_DEPENDENCY"] = "PKU338";
|
|
33
35
|
ErrorCode["REMOTE_ADDON_AUTH_UNRESOLVED"] = "PKU339";
|
|
@@ -35,6 +37,9 @@ export var ErrorCode;
|
|
|
35
37
|
ErrorCode["DB_COLUMN_NAME_TYPE_CONTRADICTION"] = "PKU480";
|
|
36
38
|
ErrorCode["DB_JSON_COLUMN_UNTYPED"] = "PKU481";
|
|
37
39
|
ErrorCode["DB_FORMAT_ON_NON_STRING"] = "PKU482";
|
|
40
|
+
ErrorCode["DB_SECRET_COLUMN_STORED_PLAIN"] = "PKU483";
|
|
41
|
+
ErrorCode["DB_FORM_ON_NON_STRING"] = "PKU484";
|
|
42
|
+
ErrorCode["DB_DEFAULT_SCHEMA_NAME_COLLISION"] = "PKU485";
|
|
38
43
|
// Configuration errors
|
|
39
44
|
ErrorCode["SCHEMA_GENERATION_ERROR"] = "PKU456";
|
|
40
45
|
ErrorCode["INLINE_SCHEMA"] = "PKU489";
|
|
@@ -43,10 +48,19 @@ export var ErrorCode;
|
|
|
43
48
|
// Auth errors
|
|
44
49
|
ErrorCode["DUPLICATE_AUTH_DEFINITION"] = "PKU581";
|
|
45
50
|
ErrorCode["AUTH_NOT_EXPORTED"] = "PKU582";
|
|
51
|
+
// Single-declaration constructs — one call site per codebase.
|
|
52
|
+
// PKU583 (defineScope) and PKU584 (defineSystemRole) are retired, not free:
|
|
53
|
+
// scopes and roles held this rule until the generated `admin` tree made it
|
|
54
|
+
// unsatisfiable, and get it back once `admin` is a default scope nobody
|
|
55
|
+
// declares. Do not reuse those two numbers for anything else.
|
|
56
|
+
ErrorCode["DUPLICATE_PERSONAS_DEFINITION"] = "PKU585";
|
|
46
57
|
// HTTP Route errors
|
|
47
58
|
ErrorCode["ROUTE_PARAM_MISMATCH"] = "PKU571";
|
|
48
59
|
ErrorCode["ROUTE_QUERY_MISMATCH"] = "PKU572";
|
|
49
60
|
ErrorCode["AUTH_DISABLED_REQUIRES_SESSIONLESS"] = "PKU573";
|
|
61
|
+
ErrorCode["EXPOSED_FUNCTION_HAS_NO_GATE"] = "PKU574";
|
|
62
|
+
ErrorCode["TAG_RESOLVES_TO_NO_MIDDLEWARE"] = "PKU575";
|
|
63
|
+
ErrorCode["PERMISSIONS_IN_BODY_NOT_ALLOWED"] = "PKU576";
|
|
50
64
|
// Versioning errors
|
|
51
65
|
ErrorCode["DUPLICATE_FUNCTION_VERSION"] = "PKU850";
|
|
52
66
|
ErrorCode["DUPLICATE_FUNCTION_NAME"] = "PKU851";
|
|
@@ -60,6 +74,10 @@ export var ErrorCode;
|
|
|
60
74
|
// Model configuration errors
|
|
61
75
|
ErrorCode["MISSING_MODEL"] = "PKU145";
|
|
62
76
|
ErrorCode["INVALID_MODEL"] = "PKU146";
|
|
77
|
+
// Agent tool reference errors
|
|
78
|
+
ErrorCode["AGENT_TOOL_UNKNOWN_NAMESPACE"] = "PKU152";
|
|
79
|
+
ErrorCode["AGENT_TOOL_NOT_FOUND"] = "PKU153";
|
|
80
|
+
ErrorCode["AGENT_TOOL_MISSING_DESCRIPTION"] = "PKU154";
|
|
63
81
|
// File structure errors
|
|
64
82
|
ErrorCode["SCHEMA_AND_WIRING_COLOCATED"] = "PKU490";
|
|
65
83
|
// Optimization diagnostics
|
|
@@ -75,4 +93,9 @@ export var ErrorCode;
|
|
|
75
93
|
ErrorCode["ADDON_WIRING_NOT_ALLOWED"] = "PKU920";
|
|
76
94
|
ErrorCode["ADDON_CONTRACT_HANDLERS_NOT_ALLOWED"] = "PKU921";
|
|
77
95
|
ErrorCode["RPC_INVOCATION_TYPE_CAST"] = "PKU940";
|
|
96
|
+
// Secret boundary errors
|
|
97
|
+
ErrorCode["SECRET_SERVICE_ALIASED"] = "PKU950";
|
|
98
|
+
ErrorCode["SECRET_NOT_DECLARED"] = "PKU951";
|
|
99
|
+
ErrorCode["SECRET_KEY_NOT_STATIC"] = "PKU952";
|
|
100
|
+
ErrorCode["SECRET_REVEALED_INTO_SINK"] = "PKU953";
|
|
78
101
|
})(ErrorCode || (ErrorCode = {}));
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export { resolveDeployTarget } from './utils/resolve-deploy-target.js';
|
|
|
14
14
|
export { generateCustomTypes, sanitizeTypeName, } from './utils/custom-types-generator.js';
|
|
15
15
|
export { createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
|
|
16
16
|
export type { ContractEntry, VersionHashEntry, VersionValidateError, VersionManifest, VersionManifestEntry, } from './utils/contract-hashes.js';
|
|
17
|
-
export type { InspectorFeature, InspectorFeatureEntry } from './types.js';
|
|
17
|
+
export type { InspectorFeature, InspectorFeatureEntry, } from './types.js';
|
|
18
18
|
export { serializeMCPJson } from './utils/serialize-mcp-json.js';
|
|
19
19
|
export type { OpenAPISpecInfo } from './utils/serialize-openapi-json.js';
|
|
20
20
|
export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/index.js';
|
package/dist/inspector.js
CHANGED
|
@@ -6,13 +6,17 @@ import { TypesMap } from './types-map.js';
|
|
|
6
6
|
import { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
7
7
|
import { findCommonAncestor } from './utils/find-root-dir.js';
|
|
8
8
|
import { createNestedProjectFilter } from './utils/nested-project-filter.js';
|
|
9
|
-
import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, validateRemoteAddonDependencies, validateRemoteAddonAuth, validateScopeReferences, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, validateSchemaWiringSeparation, validateScenarioServices, validateScenarioSteps, validateWorkflowGraphAddons, } from './utils/post-process.js';
|
|
9
|
+
import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateAgentToolReferences, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, validateRemoteAddonDependencies, validateRemoteAddonAuth, validateScopeReferences, validateSystemRoleScopes, validatePersonaRoles, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeSecretBrokers, validateNoSecretAliasServices, validateSecretUsage, computeDiagnostics, validateSchemaWiringSeparation, validateScenarioServices, validateScenarioSteps, validateWorkflowGraphAddons, } from './utils/post-process.js';
|
|
10
|
+
import { validateExposedFunctionsGated } from './utils/validate-exposed-functions-gated.js';
|
|
11
|
+
import { validateTagsResolveToMiddleware } from './utils/validate-tags-resolve-to-middleware.js';
|
|
12
|
+
import { annotateHttpRouteAuth } from './utils/annotate-http-route-auth.js';
|
|
10
13
|
import { generateOpenAPISpec } from './utils/serialize-openapi-json.js';
|
|
11
14
|
import { pikkuState } from '@pikku/core/internal';
|
|
12
15
|
import { resolveLatestVersions } from './utils/resolve-versions.js';
|
|
13
16
|
import { finalizeWorkflows } from './utils/workflow/graph/finalize-workflows.js';
|
|
14
17
|
import { finalizeWorkflowHelperTypes, finalizeWorkflowWires, } from './utils/workflow/graph/finalize-workflow-wires.js';
|
|
15
18
|
import { generateAllSchemas } from './utils/schema-generator.js';
|
|
19
|
+
import { extractSecretUsage } from './utils/extract-secret-usage.js';
|
|
16
20
|
import { loadAddonFunctionsMeta, loadAddonSchemas, } from './utils/load-addon-functions-meta.js';
|
|
17
21
|
import { computeContractHashes, extractContractsFromMeta, updateManifest, createEmptyManifest, validateContracts, } from './utils/contract-hashes.js';
|
|
18
22
|
/**
|
|
@@ -132,6 +136,7 @@ export function getInitialInspectorState(rootDir) {
|
|
|
132
136
|
secrets: {
|
|
133
137
|
definitions: [],
|
|
134
138
|
files: new Set(),
|
|
139
|
+
usage: new Map(),
|
|
135
140
|
},
|
|
136
141
|
credentials: {
|
|
137
142
|
definitions: [],
|
|
@@ -141,6 +146,14 @@ export function getInitialInspectorState(rootDir) {
|
|
|
141
146
|
definitions: [],
|
|
142
147
|
files: new Set(),
|
|
143
148
|
},
|
|
149
|
+
systemRoles: {
|
|
150
|
+
definitions: [],
|
|
151
|
+
files: new Set(),
|
|
152
|
+
},
|
|
153
|
+
personas: {
|
|
154
|
+
definitions: [],
|
|
155
|
+
files: new Set(),
|
|
156
|
+
},
|
|
144
157
|
variables: {
|
|
145
158
|
definitions: [],
|
|
146
159
|
files: new Set(),
|
|
@@ -282,6 +295,12 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
282
295
|
.sort((a, b) => a.fileName < b.fileName ? -1 : a.fileName > b.fileName ? 1 : 0);
|
|
283
296
|
logger.debug(`Got source files in ${(performance.now() - startSourceFiles).toFixed(2)}ms`);
|
|
284
297
|
const state = getInitialInspectorState(rootDir);
|
|
298
|
+
for (const sourceFile of sourceFiles) {
|
|
299
|
+
const usage = extractSecretUsage(sourceFile);
|
|
300
|
+
if (usage.keys.length > 0 || usage.dynamic.length > 0) {
|
|
301
|
+
state.secrets.usage.set(sourceFile.fileName, usage);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
285
304
|
// First sweep: add all functions
|
|
286
305
|
const startSetup = performance.now();
|
|
287
306
|
for (const sourceFile of sourceFiles) {
|
|
@@ -342,21 +361,34 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
342
361
|
computeResolvedIOTypes(state);
|
|
343
362
|
computeMiddlewareGroupsMeta(state);
|
|
344
363
|
computePermissionsGroupsMeta(state);
|
|
364
|
+
computeSecretBrokers(state);
|
|
365
|
+
validateNoSecretAliasServices(logger, checker, state);
|
|
366
|
+
validateSecretUsage(logger, state);
|
|
345
367
|
computeDiagnostics(state);
|
|
346
368
|
validateSchemaWiringSeparation(logger, state);
|
|
347
369
|
validateWorkflowGraphAddons(logger, state);
|
|
348
370
|
validateScenarioServices(logger, state);
|
|
349
371
|
validateScenarioSteps(logger, state);
|
|
372
|
+
// Needs every route, every function and every wireAddon declaration
|
|
373
|
+
// together, so it can only run once the whole program is inspected.
|
|
374
|
+
annotateHttpRouteAuth(state);
|
|
375
|
+
// Last of the validators: they read function meta and every wireAddon
|
|
376
|
+
// declaration together, so they need the whole program inspected.
|
|
377
|
+
validateExposedFunctionsGated(logger, state);
|
|
378
|
+
validateTagsResolveToMiddleware(logger, state);
|
|
350
379
|
if (options.openAPI) {
|
|
351
380
|
state.openAPISpec = await generateOpenAPISpec(logger, state.functions.meta, state.http.meta, state.schemas, options.openAPI.additionalInfo, pikkuState(null, 'misc', 'errors'));
|
|
352
381
|
}
|
|
353
382
|
validateAgentModels(logger, state);
|
|
383
|
+
validateAgentToolReferences(logger, state, options);
|
|
354
384
|
validateSecretOverrides(logger, state);
|
|
355
385
|
validateVariableOverrides(logger, state);
|
|
356
386
|
validateCredentialOverrides(logger, state);
|
|
357
387
|
validateRemoteAddonDependencies(logger, state);
|
|
358
388
|
validateRemoteAddonAuth(logger, state);
|
|
359
389
|
validateScopeReferences(logger, state);
|
|
390
|
+
validateSystemRoleScopes(logger, state);
|
|
391
|
+
validatePersonaRoles(logger, state);
|
|
360
392
|
}
|
|
361
393
|
state.program = program;
|
|
362
394
|
return state;
|