@pikku/inspector 0.12.54 → 0.12.56
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 +77 -0
- package/dist/add/add-keyed-wiring.js +3 -1
- package/dist/error-codes.d.ts +1 -0
- package/dist/error-codes.js +1 -0
- package/dist/inspector.js +7 -2
- package/dist/utils/post-process.d.ts +15 -0
- package/dist/utils/post-process.js +52 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +2 -10
- package/package.json +3 -3
- package/src/add/add-keyed-wiring.ts +3 -0
- package/src/error-codes.ts +1 -0
- package/src/inspector.ts +8 -1
- package/src/utils/post-process.ts +57 -0
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +2 -15
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,80 @@
|
|
|
1
|
+
## 0.12.56
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 7e60867: Delete exports nothing references
|
|
6
|
+
|
|
7
|
+
A sweep of the `@pikku/core` surface for exports with no consumer anywhere in the repo — no package, template, verifier or e2e project imports them: `ExtractFunctionOutput`, `CLICommandDefinition`, `RequestHeaders`, `HTTPFunctionsMeta`, `HTTPWiringMiddleware`, `JsonRpcError`, `TriggerSourceInfo`, `getMCPResources`, `getMCPPrompts`, `onGraphNodeComplete` and `InputRef`.
|
|
8
|
+
|
|
9
|
+
Every one was a compatibility promise with nothing on the other end of it. Removing them narrows what 0.13 has to keep stable.
|
|
10
|
+
|
|
11
|
+
`isRef` looked like the twelfth, and isn't. It is the type guard that reads what `createRef` writes — the `__isRef` brand marking a graph node input as "substitute another node's output here". Nothing imported it because neither it nor `RefValue` was reachable from any entry point, so the one consumer that needed it, the inspector's graph serializer, had reimplemented the same four conditions privately as `isRefValue` along with its own structural copy of `RefValue`. Deleting `isRef` would have made that duplicate permanent, with the brand's shape asserted in two places free to drift apart.
|
|
12
|
+
|
|
13
|
+
So `isRef` and `RefValue` are exported from `@pikku/core/workflow` instead, and the inspector imports them rather than keeping its own copy.
|
|
14
|
+
|
|
15
|
+
- a879ab3: **Codegen reports a schema it named but never generated (PKU463).** A named
|
|
16
|
+
contract type that is declared but not exported is imported by the virtual
|
|
17
|
+
source file the schema generator compiles, resolves to nothing, and yields no
|
|
18
|
+
schema — while the function meta still carries the name. `pikku all` exited 0
|
|
19
|
+
and the first call to that function failed in a deployment with
|
|
20
|
+
`MissingSchemaError`. The reference is now checked once addon schemas are
|
|
21
|
+
merged, since an addon supplies its own and checking earlier would report every
|
|
22
|
+
one of them as unresolved.
|
|
23
|
+
- Updated dependencies [41c1a95]
|
|
24
|
+
- Updated dependencies [ce96383]
|
|
25
|
+
- Updated dependencies [7e60867]
|
|
26
|
+
- Updated dependencies [f8f1244]
|
|
27
|
+
- Updated dependencies [dcf20cb]
|
|
28
|
+
- Updated dependencies [6512384]
|
|
29
|
+
- Updated dependencies [e3b4c14]
|
|
30
|
+
- Updated dependencies [efd0ed1]
|
|
31
|
+
- Updated dependencies [cba98fb]
|
|
32
|
+
- Updated dependencies [ce96383]
|
|
33
|
+
- Updated dependencies [f8f1244]
|
|
34
|
+
- Updated dependencies [f8f1244]
|
|
35
|
+
- Updated dependencies [6e93a35]
|
|
36
|
+
- Updated dependencies [6dada45]
|
|
37
|
+
- @pikku/core@0.12.80
|
|
38
|
+
|
|
39
|
+
## 0.12.55
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- ae4e898: Carry a secret's `allowedHosts` through code generation, and close three gaps in
|
|
44
|
+
the SSRF guard.
|
|
45
|
+
|
|
46
|
+
`allowedHosts` was declared and enforced but never survived codegen: the
|
|
47
|
+
inspector did not read the property off the `defineSecret` literal, and the meta
|
|
48
|
+
builder rebuilt its objects without it. Enforcement in `assertSecretAllowedForHost`
|
|
49
|
+
then always saw `undefined`, so the egress restriction was a no-op by default —
|
|
50
|
+
and, with `secrets.requireAllowedHosts` set, threw for every secret including the
|
|
51
|
+
ones that correctly declared hosts. Both stages now carry the field, and the
|
|
52
|
+
secrets verifier asserts it against the generated JSON rather than a hand-written
|
|
53
|
+
meta literal, which is why the existing tests stayed green.
|
|
54
|
+
|
|
55
|
+
`isPrivateHost` now checks an explicit CIDR table instead of ad-hoc octet
|
|
56
|
+
comparisons. It previously missed `100.64.0.0/10` — which contains Alibaba
|
|
57
|
+
Cloud's `100.100.100.200` metadata endpoint — along with `192.0.0.0/24`,
|
|
58
|
+
`198.18.0.0/15`, `192.88.99.0/24`, the TEST-NETs, multicast and reserved space.
|
|
59
|
+
IPv6 gains a real parser, so `fec0::/10`, `ff00::/8`, and NAT64 (`64:ff9b::/96`)
|
|
60
|
+
and 6to4 (`2002::/16`) forms wrapping an internal IPv4 address are caught.
|
|
61
|
+
|
|
62
|
+
`safeFetch` takes an optional `resolveHost`, checked on the initial URL and every
|
|
63
|
+
redirect hop, so a _public_ hostname pointing at a private address is refused —
|
|
64
|
+
the `169-254-169-254.nip.io` shape a literal-only check cannot see. Core cannot
|
|
65
|
+
resolve DNS itself (Workers has no DNS API), so the Node resolver ships as
|
|
66
|
+
`@pikku/core/node-host-resolver` and the Node server runtimes install it during
|
|
67
|
+
`init()`. The connection is not pinned to the address that was checked, so a
|
|
68
|
+
rebind between check and connect is still possible.
|
|
69
|
+
|
|
70
|
+
The graph addon's `httpRequest` node called bare `fetch`, bypassing the guard
|
|
71
|
+
entirely; it now goes through `safeFetch`.
|
|
72
|
+
|
|
73
|
+
- Updated dependencies [e848eb2]
|
|
74
|
+
- Updated dependencies [b170489]
|
|
75
|
+
- Updated dependencies [ae4e898]
|
|
76
|
+
- @pikku/core@0.12.79
|
|
77
|
+
|
|
1
78
|
## 0.12.54
|
|
2
79
|
|
|
3
80
|
### Patch Changes
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { getPropertyValue, assertStringLiteralProperty, } from '../utils/get-property-value.js';
|
|
2
|
+
import { getPropertyValue, getArrayPropertyValue, assertStringLiteralProperty, } from '../utils/get-property-value.js';
|
|
3
3
|
import { ErrorCode } from '../error-codes.js';
|
|
4
4
|
import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
|
|
5
5
|
import { parseDurationString } from '@pikku/core';
|
|
@@ -27,6 +27,7 @@ export const createAddKeyedWiring = (config) => {
|
|
|
27
27
|
const descriptionValue = getPropertyValue(obj, 'description');
|
|
28
28
|
const rotationPeriodValue = getPropertyValue(obj, 'rotationPeriod');
|
|
29
29
|
const docsUrlValue = getPropertyValue(obj, 'docsUrl');
|
|
30
|
+
const allowedHostsValue = getArrayPropertyValue(obj, 'allowedHosts');
|
|
30
31
|
const idValue = getPropertyValue(obj, config.idField);
|
|
31
32
|
let schemaVariableName = null;
|
|
32
33
|
let schemaSourceFile = null;
|
|
@@ -104,6 +105,7 @@ export const createAddKeyedWiring = (config) => {
|
|
|
104
105
|
description: descriptionValue || undefined,
|
|
105
106
|
rotationPeriod: rotationPeriodValue || undefined,
|
|
106
107
|
docsUrl: docsUrlValue || undefined,
|
|
108
|
+
allowedHosts: allowedHostsValue || undefined,
|
|
107
109
|
[config.idField]: idValue,
|
|
108
110
|
schema: schemaLookupName,
|
|
109
111
|
sourceFile,
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ export declare enum ErrorCode {
|
|
|
37
37
|
DB_FORM_ON_NON_STRING = "PKU484",
|
|
38
38
|
DB_DEFAULT_SCHEMA_NAME_COLLISION = "PKU485",
|
|
39
39
|
SCHEMA_GENERATION_ERROR = "PKU456",
|
|
40
|
+
SCHEMA_REFERENCE_UNRESOLVED = "PKU463",
|
|
40
41
|
INLINE_SCHEMA = "PKU489",
|
|
41
42
|
FUNCTION_METADATA_NOT_FOUND = "PKU559",
|
|
42
43
|
DUPLICATE_AUTH_DEFINITION = "PKU581",
|
package/dist/error-codes.js
CHANGED
|
@@ -42,6 +42,7 @@ export var ErrorCode;
|
|
|
42
42
|
ErrorCode["DB_DEFAULT_SCHEMA_NAME_COLLISION"] = "PKU485";
|
|
43
43
|
// Configuration errors
|
|
44
44
|
ErrorCode["SCHEMA_GENERATION_ERROR"] = "PKU456";
|
|
45
|
+
ErrorCode["SCHEMA_REFERENCE_UNRESOLVED"] = "PKU463";
|
|
45
46
|
ErrorCode["INLINE_SCHEMA"] = "PKU489";
|
|
46
47
|
// Function errors
|
|
47
48
|
ErrorCode["FUNCTION_METADATA_NOT_FOUND"] = "PKU559";
|
package/dist/inspector.js
CHANGED
|
@@ -6,12 +6,12 @@ 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, 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';
|
|
9
|
+
import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateAgentToolReferences, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, validateRemoteAddonDependencies, validateRemoteAddonAuth, validateScopeReferences, validateSystemRoleScopes, validatePersonaRoles, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeSecretBrokers, validateNoSecretAliasServices, validateSecretUsage, computeDiagnostics, validateSchemaReferences, validateSchemaWiringSeparation, validateScenarioServices, validateScenarioSteps, validateWorkflowGraphAddons, } from './utils/post-process.js';
|
|
10
10
|
import { validateExposedFunctionsGated } from './utils/validate-exposed-functions-gated.js';
|
|
11
11
|
import { validateTagsResolveToMiddleware } from './utils/validate-tags-resolve-to-middleware.js';
|
|
12
12
|
import { annotateHttpRouteAuth } from './utils/annotate-http-route-auth.js';
|
|
13
13
|
import { generateOpenAPISpec } from './utils/serialize-openapi-json.js';
|
|
14
|
-
import { pikkuState } from '@pikku/core/
|
|
14
|
+
import { pikkuState } from '@pikku/core/ecosystem';
|
|
15
15
|
import { resolveLatestVersions } from './utils/resolve-versions.js';
|
|
16
16
|
import { finalizeWorkflows } from './utils/workflow/graph/finalize-workflows.js';
|
|
17
17
|
import { finalizeWorkflowHelperTypes, finalizeWorkflowWires, } from './utils/workflow/graph/finalize-workflow-wires.js';
|
|
@@ -336,6 +336,11 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
336
336
|
}
|
|
337
337
|
// Re-load addon schemas (generateAllSchemas replaces state.schemas)
|
|
338
338
|
await loadAddonSchemas(logger, state);
|
|
339
|
+
// After the addon merge, not before: an addon supplies its own schemas here,
|
|
340
|
+
// and checking earlier would report every one of them as unresolved.
|
|
341
|
+
if (options.schemaConfig) {
|
|
342
|
+
validateSchemaReferences(logger, state);
|
|
343
|
+
}
|
|
339
344
|
state.manifest.initial = options.manifest ?? null;
|
|
340
345
|
const contracts = extractContractsFromMeta(state.functions.meta);
|
|
341
346
|
const baseManifest = state.manifest.initial ?? createEmptyManifest();
|
|
@@ -50,6 +50,21 @@ export declare function computeResolvedIOTypes(state: InspectorState): void;
|
|
|
50
50
|
export declare function computeMiddlewareGroupsMeta(state: InspectorState): void;
|
|
51
51
|
export declare function computePermissionsGroupsMeta(state: InspectorState): void;
|
|
52
52
|
export declare function computeRequiredSchemas(state: InspectorState, options: InspectorOptions): void;
|
|
53
|
+
/**
|
|
54
|
+
* Every schema a function's contract names has to exist once codegen is done.
|
|
55
|
+
*
|
|
56
|
+
* The way it stops existing is quiet. `generateCustomTypes` hands the schema
|
|
57
|
+
* generator a virtual source file that *imports* each named contract type, so a
|
|
58
|
+
* type that is declared but never exported resolves to nothing and yields no
|
|
59
|
+
* schema — while the function's meta still carries the name. Nothing downstream
|
|
60
|
+
* reads that name until the first call, which fails with a runtime
|
|
61
|
+
* `MissingSchemaError` in a deployment rather than in the build that caused it.
|
|
62
|
+
*
|
|
63
|
+
* Reported rather than critical: the reference is only *guaranteed* to break
|
|
64
|
+
* the functions that are actually invoked, so this blocks CI through
|
|
65
|
+
* `--fail-on-error` without failing a dev server over a function nobody calls.
|
|
66
|
+
*/
|
|
67
|
+
export declare function validateSchemaReferences(logger: InspectorLogger, state: InspectorState): void;
|
|
53
68
|
export declare function validateAgentModels(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
54
69
|
/**
|
|
55
70
|
* Scenarios are pure stories of remote RPCs (same rule as client-side CLI
|
|
@@ -554,6 +554,58 @@ export function computeRequiredSchemas(state, options) {
|
|
|
554
554
|
...Array.from(schemaLookup.keys()),
|
|
555
555
|
]);
|
|
556
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* Every schema a function's contract names has to exist once codegen is done.
|
|
559
|
+
*
|
|
560
|
+
* The way it stops existing is quiet. `generateCustomTypes` hands the schema
|
|
561
|
+
* generator a virtual source file that *imports* each named contract type, so a
|
|
562
|
+
* type that is declared but never exported resolves to nothing and yields no
|
|
563
|
+
* schema — while the function's meta still carries the name. Nothing downstream
|
|
564
|
+
* reads that name until the first call, which fails with a runtime
|
|
565
|
+
* `MissingSchemaError` in a deployment rather than in the build that caused it.
|
|
566
|
+
*
|
|
567
|
+
* Reported rather than critical: the reference is only *guaranteed* to break
|
|
568
|
+
* the functions that are actually invoked, so this blocks CI through
|
|
569
|
+
* `--fail-on-error` without failing a dev server over a function nobody calls.
|
|
570
|
+
*/
|
|
571
|
+
export function validateSchemaReferences(logger, state) {
|
|
572
|
+
const { functions, schemas } = state;
|
|
573
|
+
const available = new Set(Object.keys(schemas ?? {}));
|
|
574
|
+
const unresolved = [];
|
|
575
|
+
const resolve = (typeName) => {
|
|
576
|
+
try {
|
|
577
|
+
return functions.typesMap.getUniqueName(typeName);
|
|
578
|
+
}
|
|
579
|
+
catch {
|
|
580
|
+
return typeName;
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
for (const [funcName, meta] of Object.entries(functions.meta)) {
|
|
584
|
+
for (const [role, declared] of [
|
|
585
|
+
['input', meta.inputs?.[0]],
|
|
586
|
+
['output', meta.outputs?.[0]],
|
|
587
|
+
]) {
|
|
588
|
+
if (!declared || PRIMITIVE_TYPES.has(declared))
|
|
589
|
+
continue;
|
|
590
|
+
const resolved = resolve(declared);
|
|
591
|
+
if (PRIMITIVE_TYPES.has(resolved) || available.has(resolved))
|
|
592
|
+
continue;
|
|
593
|
+
unresolved.push(`${funcName}.${role} → '${resolved}'`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (unresolved.length === 0)
|
|
597
|
+
return;
|
|
598
|
+
logger.diagnostic({
|
|
599
|
+
severity: 'error',
|
|
600
|
+
code: ErrorCode.SCHEMA_REFERENCE_UNRESOLVED,
|
|
601
|
+
message: `${unresolved.length} function contract${unresolved.length === 1 ? '' : 's'} ` +
|
|
602
|
+
`name${unresolved.length === 1 ? 's' : ''} a schema that was never generated, so ` +
|
|
603
|
+
`calling ${unresolved.length === 1 ? 'it' : 'them'} fails at runtime with ` +
|
|
604
|
+
`MissingSchemaError: ${unresolved.join(', ')}. ` +
|
|
605
|
+
`The usual cause is a named type used as the function's input/output that ` +
|
|
606
|
+
`is declared but not exported — export it, or inline the type at the call site.`,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
557
609
|
export function validateAgentModels(logger, state) {
|
|
558
610
|
for (const [, meta] of Object.entries(state.agents.agentsMeta)) {
|
|
559
611
|
const model = meta.model;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRef } from '@pikku/core/workflow';
|
|
1
2
|
/**
|
|
2
3
|
* Convert a RefValue (from runtime) to DataRef (serialized)
|
|
3
4
|
*/
|
|
@@ -7,22 +8,13 @@ function convertRef(ref) {
|
|
|
7
8
|
path: ref.path,
|
|
8
9
|
};
|
|
9
10
|
}
|
|
10
|
-
/**
|
|
11
|
-
* Check if a value is a runtime RefValue
|
|
12
|
-
*/
|
|
13
|
-
function isRefValue(value) {
|
|
14
|
-
return (typeof value === 'object' &&
|
|
15
|
-
value !== null &&
|
|
16
|
-
'__isRef' in value &&
|
|
17
|
-
value.__isRef === true);
|
|
18
|
-
}
|
|
19
11
|
/**
|
|
20
12
|
* Convert input mapping from runtime format to serialized format
|
|
21
13
|
*/
|
|
22
14
|
function serializeInputMapping(input) {
|
|
23
15
|
const result = {};
|
|
24
16
|
for (const [key, value] of Object.entries(input)) {
|
|
25
|
-
if (
|
|
17
|
+
if (isRef(value)) {
|
|
26
18
|
result[key] = convertRef(value);
|
|
27
19
|
}
|
|
28
20
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.56",
|
|
4
4
|
"description": "TypeScript inspector for Pikku — discovers functions and wirings to drive code generation",
|
|
5
5
|
"author": "yasser.fadl@gmail.com",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"zod-to-ts": "^2.1.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@pikku/core": "^0.12.
|
|
48
|
+
"@pikku/core": "^0.12.80"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@pikku/core": "^0.12.
|
|
51
|
+
"@pikku/core": "^0.12.80",
|
|
52
52
|
"@types/node": "^24.11.0"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as ts from 'typescript'
|
|
2
2
|
import {
|
|
3
3
|
getPropertyValue,
|
|
4
|
+
getArrayPropertyValue,
|
|
4
5
|
assertStringLiteralProperty,
|
|
5
6
|
} from '../utils/get-property-value.js'
|
|
6
7
|
import type { AddWiring, InspectorState } from '../types.js'
|
|
@@ -57,6 +58,7 @@ export const createAddKeyedWiring = (config: KeyedWiringConfig): AddWiring => {
|
|
|
57
58
|
| string
|
|
58
59
|
| null
|
|
59
60
|
const docsUrlValue = getPropertyValue(obj, 'docsUrl') as string | null
|
|
61
|
+
const allowedHostsValue = getArrayPropertyValue(obj, 'allowedHosts')
|
|
60
62
|
const idValue = getPropertyValue(obj, config.idField) as string | null
|
|
61
63
|
|
|
62
64
|
let schemaVariableName: string | null = null
|
|
@@ -167,6 +169,7 @@ export const createAddKeyedWiring = (config: KeyedWiringConfig): AddWiring => {
|
|
|
167
169
|
description: descriptionValue || undefined,
|
|
168
170
|
rotationPeriod: rotationPeriodValue || undefined,
|
|
169
171
|
docsUrl: docsUrlValue || undefined,
|
|
172
|
+
allowedHosts: allowedHostsValue || undefined,
|
|
170
173
|
[config.idField]: idValue,
|
|
171
174
|
schema: schemaLookupName,
|
|
172
175
|
sourceFile,
|
package/src/error-codes.ts
CHANGED
package/src/inspector.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
validateNoSecretAliasServices,
|
|
33
33
|
validateSecretUsage,
|
|
34
34
|
computeDiagnostics,
|
|
35
|
+
validateSchemaReferences,
|
|
35
36
|
validateSchemaWiringSeparation,
|
|
36
37
|
validateScenarioServices,
|
|
37
38
|
validateScenarioSteps,
|
|
@@ -41,7 +42,7 @@ import { validateExposedFunctionsGated } from './utils/validate-exposed-function
|
|
|
41
42
|
import { validateTagsResolveToMiddleware } from './utils/validate-tags-resolve-to-middleware.js'
|
|
42
43
|
import { annotateHttpRouteAuth } from './utils/annotate-http-route-auth.js'
|
|
43
44
|
import { generateOpenAPISpec } from './utils/serialize-openapi-json.js'
|
|
44
|
-
import { pikkuState } from '@pikku/core/
|
|
45
|
+
import { pikkuState } from '@pikku/core/ecosystem'
|
|
45
46
|
import { resolveLatestVersions } from './utils/resolve-versions.js'
|
|
46
47
|
import { finalizeWorkflows } from './utils/workflow/graph/finalize-workflows.js'
|
|
47
48
|
import {
|
|
@@ -444,6 +445,12 @@ export const inspect = async (
|
|
|
444
445
|
// Re-load addon schemas (generateAllSchemas replaces state.schemas)
|
|
445
446
|
await loadAddonSchemas(logger, state)
|
|
446
447
|
|
|
448
|
+
// After the addon merge, not before: an addon supplies its own schemas here,
|
|
449
|
+
// and checking earlier would report every one of them as unresolved.
|
|
450
|
+
if (options.schemaConfig) {
|
|
451
|
+
validateSchemaReferences(logger, state)
|
|
452
|
+
}
|
|
453
|
+
|
|
447
454
|
state.manifest.initial = options.manifest ?? null
|
|
448
455
|
const contracts = extractContractsFromMeta(state.functions.meta)
|
|
449
456
|
const baseManifest = state.manifest.initial ?? createEmptyManifest()
|
|
@@ -689,6 +689,63 @@ export function computeRequiredSchemas(
|
|
|
689
689
|
])
|
|
690
690
|
}
|
|
691
691
|
|
|
692
|
+
/**
|
|
693
|
+
* Every schema a function's contract names has to exist once codegen is done.
|
|
694
|
+
*
|
|
695
|
+
* The way it stops existing is quiet. `generateCustomTypes` hands the schema
|
|
696
|
+
* generator a virtual source file that *imports* each named contract type, so a
|
|
697
|
+
* type that is declared but never exported resolves to nothing and yields no
|
|
698
|
+
* schema — while the function's meta still carries the name. Nothing downstream
|
|
699
|
+
* reads that name until the first call, which fails with a runtime
|
|
700
|
+
* `MissingSchemaError` in a deployment rather than in the build that caused it.
|
|
701
|
+
*
|
|
702
|
+
* Reported rather than critical: the reference is only *guaranteed* to break
|
|
703
|
+
* the functions that are actually invoked, so this blocks CI through
|
|
704
|
+
* `--fail-on-error` without failing a dev server over a function nobody calls.
|
|
705
|
+
*/
|
|
706
|
+
export function validateSchemaReferences(
|
|
707
|
+
logger: InspectorLogger,
|
|
708
|
+
state: InspectorState
|
|
709
|
+
): void {
|
|
710
|
+
const { functions, schemas } = state
|
|
711
|
+
const available = new Set(Object.keys(schemas ?? {}))
|
|
712
|
+
const unresolved: string[] = []
|
|
713
|
+
|
|
714
|
+
const resolve = (typeName: string): string => {
|
|
715
|
+
try {
|
|
716
|
+
return functions.typesMap.getUniqueName(typeName)
|
|
717
|
+
} catch {
|
|
718
|
+
return typeName
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
for (const [funcName, meta] of Object.entries(functions.meta)) {
|
|
723
|
+
for (const [role, declared] of [
|
|
724
|
+
['input', meta.inputs?.[0]],
|
|
725
|
+
['output', meta.outputs?.[0]],
|
|
726
|
+
] as const) {
|
|
727
|
+
if (!declared || PRIMITIVE_TYPES.has(declared)) continue
|
|
728
|
+
const resolved = resolve(declared)
|
|
729
|
+
if (PRIMITIVE_TYPES.has(resolved) || available.has(resolved)) continue
|
|
730
|
+
unresolved.push(`${funcName}.${role} → '${resolved}'`)
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (unresolved.length === 0) return
|
|
735
|
+
|
|
736
|
+
logger.diagnostic({
|
|
737
|
+
severity: 'error',
|
|
738
|
+
code: ErrorCode.SCHEMA_REFERENCE_UNRESOLVED,
|
|
739
|
+
message:
|
|
740
|
+
`${unresolved.length} function contract${unresolved.length === 1 ? '' : 's'} ` +
|
|
741
|
+
`name${unresolved.length === 1 ? 's' : ''} a schema that was never generated, so ` +
|
|
742
|
+
`calling ${unresolved.length === 1 ? 'it' : 'them'} fails at runtime with ` +
|
|
743
|
+
`MissingSchemaError: ${unresolved.join(', ')}. ` +
|
|
744
|
+
`The usual cause is a named type used as the function's input/output that ` +
|
|
745
|
+
`is declared but not exported — export it, or inline the type at the call site.`,
|
|
746
|
+
})
|
|
747
|
+
}
|
|
748
|
+
|
|
692
749
|
export function validateAgentModels(
|
|
693
750
|
logger: InspectorLogger,
|
|
694
751
|
state: InspectorState | Omit<InspectorState, 'typesLookup'>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRef } from '@pikku/core/workflow'
|
|
1
2
|
import type {
|
|
2
3
|
SerializedWorkflowGraph,
|
|
3
4
|
SerializedGraphNode,
|
|
@@ -16,20 +17,6 @@ function convertRef(ref: { nodeId: string; path?: string }): DataRef {
|
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
/**
|
|
20
|
-
* Check if a value is a runtime RefValue
|
|
21
|
-
*/
|
|
22
|
-
function isRefValue(
|
|
23
|
-
value: unknown
|
|
24
|
-
): value is { __isRef: true; nodeId: string; path?: string } {
|
|
25
|
-
return (
|
|
26
|
-
typeof value === 'object' &&
|
|
27
|
-
value !== null &&
|
|
28
|
-
'__isRef' in value &&
|
|
29
|
-
(value as any).__isRef === true
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
20
|
/**
|
|
34
21
|
* Convert input mapping from runtime format to serialized format
|
|
35
22
|
*/
|
|
@@ -39,7 +26,7 @@ function serializeInputMapping(
|
|
|
39
26
|
const result: Record<string, unknown | DataRef> = {}
|
|
40
27
|
|
|
41
28
|
for (const [key, value] of Object.entries(input)) {
|
|
42
|
-
if (
|
|
29
|
+
if (isRef(value)) {
|
|
43
30
|
result[key] = convertRef(value)
|
|
44
31
|
} else {
|
|
45
32
|
result[key] = value
|