@farmslot/protocol 0.11.0 → 0.12.0
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 +13 -0
- package/README.md +39 -21
- package/dist/recipe/artifact.d.ts.map +1 -1
- package/dist/recipe/artifact.js +133 -4
- package/dist/recipe/artifact.js.map +1 -1
- package/dist/recipe/common.d.ts +7 -30
- package/dist/recipe/common.d.ts.map +1 -1
- package/dist/recipe/common.js +2 -1
- package/dist/recipe/common.js.map +1 -1
- package/dist/recipe/digest.d.ts.map +1 -1
- package/dist/recipe/digest.js +3 -2
- package/dist/recipe/digest.js.map +1 -1
- package/dist/recipe/document.d.ts +0 -2
- package/dist/recipe/document.d.ts.map +1 -1
- package/dist/recipe/document.js +12 -33
- package/dist/recipe/document.js.map +1 -1
- package/dist/recipe/index.d.ts +1 -1
- package/dist/recipe/index.d.ts.map +1 -1
- package/dist/recipe/index.js +1 -1
- package/dist/recipe/index.js.map +1 -1
- package/dist/recipe/manifest.d.ts.map +1 -1
- package/dist/recipe/manifest.js +102 -253
- package/dist/recipe/manifest.js.map +1 -1
- package/dist/recipe/params.d.ts.map +1 -1
- package/dist/recipe/params.js +22 -19
- package/dist/recipe/params.js.map +1 -1
- package/dist/recipe/trust.d.ts +3 -1
- package/dist/recipe/trust.d.ts.map +1 -1
- package/dist/recipe/trust.js.map +1 -1
- package/dist/recipe/workflow.d.ts +3 -1
- package/dist/recipe/workflow.d.ts.map +1 -1
- package/dist/recipe/workflow.js +32 -7
- package/dist/recipe/workflow.js.map +1 -1
- package/package.json +5 -1
- package/schemas/action-manifest-v1.schema.json +441 -0
- package/schemas/recipe-v1.schema.json +140 -35
package/dist/recipe/document.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { addFinding, createContext, finishResult, hasOwn, isNonEmptyString, isRecord, RECIPE_PROTOCOL_SCHEMA_URL,
|
|
1
|
+
import { addFinding, createContext, finishResult, hasOwn, isNonEmptyString, isRecord, RECIPE_PROTOCOL_SCHEMA_URL, validateOptionalStringField, } from './common.js';
|
|
2
2
|
import { getRecipeActionManifestActionNames, validateRecipeActionManifestDocument, } from './manifest.js';
|
|
3
3
|
import { validateRecipeParams, validateRecipeParamsSchema } from './params.js';
|
|
4
|
-
import { extractWorkflow, getRecipeActionParams, getRecipeWorkflowActionEntries, validateRecipeCalls, validateWorkflowGraph, } from './workflow.js';
|
|
4
|
+
import { extractWorkflow, getRecipeActionParams, getRecipeWorkflowActionEntries, validateRecipeActionCases, validateRecipeCalls, validateWorkflowGraph, } from './workflow.js';
|
|
5
5
|
const RECIPE_FIELDS = new Set([
|
|
6
6
|
'$schema',
|
|
7
7
|
'title',
|
|
@@ -49,19 +49,7 @@ export function validateRecipeWithManifest(recipe, manifest, options) {
|
|
|
49
49
|
});
|
|
50
50
|
ctx.findings.push(...paramsResult.findings.map((finding) => rebaseFinding(finding, path)));
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
if (!contract?.resultCases?.length) {
|
|
54
|
-
addFinding(ctx, 'error', 'recipe.action_cases_not_declared', `${path}.cases`, `Action ${action} uses result cases but its manifest declares none.`);
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
const allowed = new Set(contract.resultCases);
|
|
58
|
-
for (const caseName of Object.keys(node.cases)) {
|
|
59
|
-
if (!allowed.has(caseName)) {
|
|
60
|
-
addFinding(ctx, 'error', 'recipe.action_case_not_declared', `${path}.cases.${caseName}`, `Case ${caseName} is not declared by action ${action}.`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
52
|
+
ctx.findings.push(...validateRecipeActionCases(node, action, contract?.resultCases, path).findings);
|
|
65
53
|
}
|
|
66
54
|
return finishResult(ctx);
|
|
67
55
|
}
|
|
@@ -82,12 +70,14 @@ export function validateRecipeDocument(recipe, options) {
|
|
|
82
70
|
else if (recipe.$schema !== RECIPE_PROTOCOL_SCHEMA_URL) {
|
|
83
71
|
addFinding(ctx, 'error', 'recipe.unsupported_schema_ref', '$schema', `Recipe $schema must equal ${RECIPE_PROTOCOL_SCHEMA_URL}.`);
|
|
84
72
|
}
|
|
85
|
-
|
|
73
|
+
validateOptionalStringField(ctx, recipe, 'description', 'description');
|
|
86
74
|
validateOptionalStringField(ctx, recipe, 'title', 'title');
|
|
87
75
|
if (hasOwn(recipe, 'paramsSchema')) {
|
|
88
76
|
ctx.findings.push(...validateRecipeParamsSchema(recipe.paramsSchema).findings);
|
|
89
77
|
}
|
|
90
|
-
const proofTargetIds =
|
|
78
|
+
const proofTargetIds = hasOwn(recipe, 'proofTargets')
|
|
79
|
+
? validateProofTargets(ctx, recipe.proofTargets)
|
|
80
|
+
: new Set();
|
|
91
81
|
const workflow = extractWorkflow(ctx, recipe);
|
|
92
82
|
if (workflow) {
|
|
93
83
|
validateWorkflowGraph(ctx, workflow);
|
|
@@ -101,8 +91,6 @@ export function validateRecipeDocument(recipe, options) {
|
|
|
101
91
|
}
|
|
102
92
|
function validateProofTargets(ctx, value) {
|
|
103
93
|
const ids = new Set();
|
|
104
|
-
if (value == null)
|
|
105
|
-
return ids;
|
|
106
94
|
if (!Array.isArray(value)) {
|
|
107
95
|
addFinding(ctx, 'error', 'recipe.invalid_proof_targets', 'proofTargets', 'proofTargets must be an array.');
|
|
108
96
|
return ids;
|
|
@@ -155,21 +143,12 @@ function validateProofLinks(ctx, nodes, declared) {
|
|
|
155
143
|
}
|
|
156
144
|
function manifestActionContracts(manifest) {
|
|
157
145
|
const contracts = new Map();
|
|
158
|
-
if (!isRecord(manifest))
|
|
146
|
+
if (!isRecord(manifest) || !isRecord(manifest.actions))
|
|
159
147
|
return contracts;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
contracts.set(name, actionContract(entry));
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (Array.isArray(manifest.custom_actions)) {
|
|
168
|
-
for (const entry of manifest.custom_actions) {
|
|
169
|
-
if (isRecord(entry) && isNonEmptyString(entry.name)) {
|
|
170
|
-
contracts.set(entry.name, actionContract(entry));
|
|
171
|
-
}
|
|
172
|
-
}
|
|
148
|
+
for (const [name, entry] of Object.entries(manifest.actions)) {
|
|
149
|
+
if (!isRecord(entry))
|
|
150
|
+
continue;
|
|
151
|
+
contracts.set(name, actionContract(entry));
|
|
173
152
|
}
|
|
174
153
|
return contracts;
|
|
175
154
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/recipe/document.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,aAAa,EACb,YAAY,EACZ,MAAM,EACN,gBAAgB,EAChB,QAAQ,EACR,0BAA0B,EAG1B,
|
|
1
|
+
{"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/recipe/document.ts"],"names":[],"mappings":"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"}
|
package/dist/recipe/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { mergeRecipeValidationResults, validateArtifactManifestDocument, validateRecipeArtifactPackage, } from './artifact.js';
|
|
2
|
-
export { BUILT_IN_UI_OBSERVERS, type BuiltInUiObserverRef, isRecord, OFFICIAL_RECIPE_ACTIONS, type OfficialActionName, RECIPE_PROTOCOL_SCHEMA_URL, RECIPE_PROTOCOL_SCHEMA_URLS, RECIPE_PROTOCOL_SCHEMA_VERSION, type RecipeActionCatalogEntry, type
|
|
2
|
+
export { BUILT_IN_UI_OBSERVERS, type BuiltInUiObserverRef, isRecord, OFFICIAL_RECIPE_ACTIONS, type OfficialActionName, RECIPE_ACTION_MANIFEST_SCHEMA_URL, RECIPE_PROTOCOL_SCHEMA_URL, RECIPE_PROTOCOL_SCHEMA_URLS, RECIPE_PROTOCOL_SCHEMA_VERSION, type RecipeActionCatalogEntry, type RecipeActionManifestDocument, type RecipeActionName, type RecipeArtifactPackageInput, type RecipeObserverDeclaration, type RecipeResolutionDependency, type RecipeResolutionDocument, type RecipeResolutionEdge, type RecipeRuntimeCapabilityDeclaration, type RecipeValidationFinding, type RecipeValidationResult, type RecipeValidationSeverity, type RecipeValidationStatus, type UiObserverRef, } from './common.js';
|
|
3
3
|
export { recipeProtocolSchemaUrlForVersion } from './common.js';
|
|
4
4
|
export { canonicalRecipeJson, digestRecipeDocument, resolvedRecipeArtifactPath } from './digest.js';
|
|
5
5
|
export { type RecipeDocumentValidationOptions, validateRecipeDocument, validateRecipeWithManifest, validateResolvedRecipeActionNode, } from './document.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/recipe/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,gCAAgC,EAChC,6BAA6B,GAC9B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,qBAAqB,EACrB,KAAK,oBAAoB,EACzB,QAAQ,EACR,uBAAuB,EACvB,KAAK,kBAAkB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,8BAA8B,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/recipe/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,gCAAgC,EAChC,6BAA6B,GAC9B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,qBAAqB,EACrB,KAAK,oBAAoB,EACzB,QAAQ,EACR,uBAAuB,EACvB,KAAK,kBAAkB,EACvB,iCAAiC,EACjC,0BAA0B,EAC1B,2BAA2B,EAC3B,8BAA8B,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,4BAA4B,EACjC,KAAK,gBAAgB,EACrB,KAAK,0BAA0B,EAC/B,KAAK,yBAAyB,EAC9B,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,kCAAkC,EACvC,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iCAAiC,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AACpG,OAAO,EACL,KAAK,+BAA+B,EACpC,sBAAsB,EACtB,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kCAAkC,EAClC,oCAAoC,GACrC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,6CAA6C,EAC7C,gCAAgC,EAChC,6BAA6B,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,wBAAwB,EACxB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,eAAe,CAAC"}
|
package/dist/recipe/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { mergeRecipeValidationResults, validateArtifactManifestDocument, validateRecipeArtifactPackage, } from './artifact.js';
|
|
2
|
-
export { BUILT_IN_UI_OBSERVERS, isRecord, OFFICIAL_RECIPE_ACTIONS, RECIPE_PROTOCOL_SCHEMA_URL, RECIPE_PROTOCOL_SCHEMA_URLS, RECIPE_PROTOCOL_SCHEMA_VERSION, } from './common.js';
|
|
2
|
+
export { BUILT_IN_UI_OBSERVERS, isRecord, OFFICIAL_RECIPE_ACTIONS, RECIPE_ACTION_MANIFEST_SCHEMA_URL, RECIPE_PROTOCOL_SCHEMA_URL, RECIPE_PROTOCOL_SCHEMA_URLS, RECIPE_PROTOCOL_SCHEMA_VERSION, } from './common.js';
|
|
3
3
|
export { recipeProtocolSchemaUrlForVersion } from './common.js';
|
|
4
4
|
export { canonicalRecipeJson, digestRecipeDocument, resolvedRecipeArtifactPath } from './digest.js';
|
|
5
5
|
export { validateRecipeDocument, validateRecipeWithManifest, validateResolvedRecipeActionNode, } from './document.js';
|
package/dist/recipe/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/recipe/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,gCAAgC,EAChC,6BAA6B,GAC9B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,qBAAqB,EAErB,QAAQ,EACR,uBAAuB,EAEvB,0BAA0B,EAC1B,2BAA2B,EAC3B,8BAA8B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/recipe/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,gCAAgC,EAChC,6BAA6B,GAC9B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,qBAAqB,EAErB,QAAQ,EACR,uBAAuB,EAEvB,iCAAiC,EACjC,0BAA0B,EAC1B,2BAA2B,EAC3B,8BAA8B,GAe/B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iCAAiC,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AACpG,OAAO,EAEL,sBAAsB,EACtB,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kCAAkC,EAClC,oCAAoC,GACrC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,6CAA6C,EAC7C,gCAAgC,EAChC,6BAA6B,GAS9B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,wBAAwB,EACxB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,eAAe,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/recipe/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,KAAK,sBAAsB,EAC5B,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/recipe/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,KAAK,sBAAsB,EAC5B,MAAM,aAAa,CAAC;AAqBrB,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,CAG9E;AA4QD,wBAAgB,oCAAoC,CAAC,QAAQ,EAAE,OAAO,GAAG,sBAAsB,CAoF9F"}
|
package/dist/recipe/manifest.js
CHANGED
|
@@ -1,54 +1,40 @@
|
|
|
1
|
-
import { addFinding, BUILT_IN_UI_OBSERVER_SET, createContext, finishResult, isNonEmptyString, isRecord, OFFICIAL_ACTION_SET,
|
|
1
|
+
import { addFinding, BUILT_IN_UI_OBSERVER_SET, createContext, finishResult, isNonEmptyString, isRecord, OFFICIAL_ACTION_SET, RECIPE_ACTION_MANIFEST_SCHEMA_URL, } from './common.js';
|
|
2
2
|
import { validateRecipeParams, validateRecipeParamsSchema } from './params.js';
|
|
3
3
|
import { RECIPE_EXECUTION_CAPABILITIES } from './trust.js';
|
|
4
|
-
import { getRecipeActionParams } from './workflow.js';
|
|
4
|
+
import { getRecipeActionParams, isDynamicRecipeRef, validateRecipeActionCases, validateRecipeWorkflowNode, } from './workflow.js';
|
|
5
5
|
const recipeExecutionCapabilitySet = new Set(RECIPE_EXECUTION_CAPABILITIES);
|
|
6
|
-
const ACTION_MANIFEST_FIELDS = new Set([
|
|
7
|
-
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'capabilities',
|
|
14
|
-
'observers',
|
|
6
|
+
const ACTION_MANIFEST_FIELDS = new Set(['$schema', 'actions', 'observers']);
|
|
7
|
+
const ACTION_ENTRY_FIELDS = new Set([
|
|
8
|
+
'description',
|
|
9
|
+
'schema',
|
|
10
|
+
'result_cases',
|
|
11
|
+
'examples',
|
|
12
|
+
'execution_capabilities',
|
|
15
13
|
]);
|
|
14
|
+
const OBSERVER_FIELDS = new Set(['ref', 'default_for']);
|
|
16
15
|
export function getRecipeActionManifestActionNames(manifest) {
|
|
17
|
-
if (!isRecord(manifest))
|
|
16
|
+
if (!isRecord(manifest) || !isRecord(manifest.actions))
|
|
18
17
|
return [];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (Array.isArray(manifest.custom_actions)) {
|
|
27
|
-
for (const action of manifest.custom_actions) {
|
|
28
|
-
if (isRecord(action) && isNonEmptyString(action.name))
|
|
29
|
-
actionNames.add(action.name);
|
|
30
|
-
}
|
|
18
|
+
return Object.keys(manifest.actions).sort();
|
|
19
|
+
}
|
|
20
|
+
function rejectUnknownFields(ctx, value, allowed, path, code) {
|
|
21
|
+
for (const field of Object.keys(value)) {
|
|
22
|
+
if (allowed.has(field))
|
|
23
|
+
continue;
|
|
24
|
+
addFinding(ctx, 'error', code, `${path}.${field}`, `${path} does not support ${field}.`);
|
|
31
25
|
}
|
|
32
|
-
return [...actionNames].sort();
|
|
33
26
|
}
|
|
34
|
-
function validateActionCatalogEntry(ctx, entry, path
|
|
27
|
+
function validateActionCatalogEntry(ctx, action, entry, path) {
|
|
35
28
|
if (!isRecord(entry)) {
|
|
36
|
-
addFinding(ctx, 'error', 'action_manifest.
|
|
29
|
+
addFinding(ctx, 'error', 'action_manifest.invalid_action', path, `${path} must be an object.`);
|
|
37
30
|
return;
|
|
38
31
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
'
|
|
42
|
-
'avoid_when',
|
|
43
|
-
'proof_effect',
|
|
44
|
-
'safety_notes',
|
|
45
|
-
]) {
|
|
46
|
-
const value = entry[stringField];
|
|
47
|
-
if (value != null && typeof value !== 'string') {
|
|
48
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_metadata_field', `${path}.${stringField}`, `${path}.${stringField} must be a string when present.`);
|
|
49
|
-
}
|
|
32
|
+
rejectUnknownFields(ctx, entry, ACTION_ENTRY_FIELDS, path, 'action_manifest.unsupported_action_field');
|
|
33
|
+
if (!isNonEmptyString(entry.description)) {
|
|
34
|
+
addFinding(ctx, 'error', 'action_manifest.missing_description', `${path}.description`, `${path}.description must be a non-empty string.`);
|
|
50
35
|
}
|
|
51
|
-
|
|
36
|
+
const schemaRequired = action !== 'call' && action !== 'end';
|
|
37
|
+
if (!Object.hasOwn(entry, 'schema')) {
|
|
52
38
|
if (schemaRequired) {
|
|
53
39
|
addFinding(ctx, 'error', 'action_manifest.missing_schema', `${path}.schema`, `${path}.schema is required so action parameters can be validated before execution.`);
|
|
54
40
|
}
|
|
@@ -57,13 +43,13 @@ function validateActionCatalogEntry(ctx, entry, path, expectedAction, schemaRequ
|
|
|
57
43
|
addFinding(ctx, 'error', 'action_manifest.invalid_schema', `${path}.schema`, `${path}.schema must be a JSON Schema object when present.`);
|
|
58
44
|
}
|
|
59
45
|
else {
|
|
60
|
-
const schemaResult = validateRecipeParamsSchema(entry.schema
|
|
46
|
+
const schemaResult = validateRecipeParamsSchema(entry.schema);
|
|
61
47
|
ctx.findings.push(...schemaResult.findings.map((finding) => ({
|
|
62
48
|
...finding,
|
|
63
49
|
path: `${path}.schema${finding.path === 'paramsSchema' ? '' : finding.path.replace(/^paramsSchema/u, '')}`,
|
|
64
50
|
})));
|
|
65
51
|
}
|
|
66
|
-
if (entry
|
|
52
|
+
if (Object.hasOwn(entry, 'result_cases')) {
|
|
67
53
|
if (!Array.isArray(entry.result_cases) ||
|
|
68
54
|
entry.result_cases.length === 0 ||
|
|
69
55
|
entry.result_cases.some((value) => !isNonEmptyString(value)) ||
|
|
@@ -71,9 +57,10 @@ function validateActionCatalogEntry(ctx, entry, path, expectedAction, schemaRequ
|
|
|
71
57
|
addFinding(ctx, 'error', 'action_manifest.invalid_result_cases', `${path}.result_cases`, `${path}.result_cases must be a non-empty array of unique case names.`);
|
|
72
58
|
}
|
|
73
59
|
}
|
|
74
|
-
if (entry
|
|
75
|
-
if (!Array.isArray(entry.execution_capabilities)
|
|
76
|
-
|
|
60
|
+
if (Object.hasOwn(entry, 'execution_capabilities')) {
|
|
61
|
+
if (!Array.isArray(entry.execution_capabilities) ||
|
|
62
|
+
new Set(entry.execution_capabilities).size !== entry.execution_capabilities.length) {
|
|
63
|
+
addFinding(ctx, 'error', 'action_manifest.invalid_execution_capabilities', `${path}.execution_capabilities`, `${path}.execution_capabilities must be an array of unique capabilities.`);
|
|
77
64
|
}
|
|
78
65
|
else {
|
|
79
66
|
entry.execution_capabilities.forEach((capability, index) => {
|
|
@@ -83,249 +70,111 @@ function validateActionCatalogEntry(ctx, entry, path, expectedAction, schemaRequ
|
|
|
83
70
|
});
|
|
84
71
|
}
|
|
85
72
|
}
|
|
86
|
-
if (entry.examples
|
|
87
|
-
|
|
88
|
-
if (!Array.isArray(entry.examples)) {
|
|
89
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_examples', `${path}.examples`, `${path}.examples must be an array when present.`);
|
|
73
|
+
if (!Array.isArray(entry.examples) || entry.examples.length === 0) {
|
|
74
|
+
addFinding(ctx, 'error', 'action_manifest.missing_examples', `${path}.examples`, `${path}.examples must contain at least one copyable recipe node.`);
|
|
90
75
|
return;
|
|
91
76
|
}
|
|
92
77
|
entry.examples.forEach((example, index) => {
|
|
93
78
|
const examplePath = `${path}.examples[${index}]`;
|
|
94
79
|
if (!isRecord(example)) {
|
|
95
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_example', examplePath, `${examplePath} must be
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (example.description != null && typeof example.description !== 'string') {
|
|
99
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_example_description', `${examplePath}.description`, `${examplePath}.description must be a string when present.`);
|
|
100
|
-
}
|
|
101
|
-
if (!isRecord(example.node)) {
|
|
102
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_example_node', `${examplePath}.node`, `${examplePath}.node must be an object.`);
|
|
80
|
+
addFinding(ctx, 'error', 'action_manifest.invalid_example', examplePath, `${examplePath} must be a recipe node object.`);
|
|
103
81
|
return;
|
|
104
82
|
}
|
|
105
|
-
if (
|
|
106
|
-
addFinding(ctx, 'error', 'action_manifest.
|
|
107
|
-
}
|
|
108
|
-
else if (expectedAction && example.node.action !== expectedAction) {
|
|
109
|
-
addFinding(ctx, 'error', 'action_manifest.example_action_mismatch', `${examplePath}.node.action`, `Example action ${example.node.action} must match declared action ${expectedAction}.`);
|
|
83
|
+
if (example.action !== action) {
|
|
84
|
+
addFinding(ctx, 'error', 'action_manifest.example_action_mismatch', `${examplePath}.action`, `${examplePath}.action must equal ${action}.`);
|
|
110
85
|
}
|
|
86
|
+
const nodeResult = validateRecipeWorkflowNode('example', example);
|
|
87
|
+
ctx.findings.push(...nodeResult.findings.map((finding) => ({
|
|
88
|
+
...finding,
|
|
89
|
+
path: finding.path.replace(/^workflow\.nodes\.example/u, examplePath),
|
|
90
|
+
})));
|
|
91
|
+
if (action === 'call' && isNonEmptyString(example.ref) && isDynamicRecipeRef(example.ref)) {
|
|
92
|
+
addFinding(ctx, 'error', 'workflow.dynamic_call_ref', `${examplePath}.ref`, 'call.ref must be static so dependencies can be resolved and trusted before execution.');
|
|
93
|
+
}
|
|
94
|
+
const resultCases = Array.isArray(entry.result_cases)
|
|
95
|
+
? entry.result_cases.filter(isNonEmptyString)
|
|
96
|
+
: undefined;
|
|
97
|
+
const casesResult = validateRecipeActionCases(example, action, resultCases, examplePath);
|
|
98
|
+
ctx.findings.push(...casesResult.findings);
|
|
111
99
|
if (isRecord(entry.schema)) {
|
|
112
|
-
const paramsResult = validateRecipeParams(getRecipeActionParams(example
|
|
100
|
+
const paramsResult = validateRecipeParams(getRecipeActionParams(example), entry.schema, {
|
|
113
101
|
allowTemplates: true,
|
|
114
102
|
});
|
|
115
103
|
ctx.findings.push(...paramsResult.findings.map((finding) => ({
|
|
116
104
|
...finding,
|
|
117
|
-
path: `${examplePath}
|
|
105
|
+
path: `${examplePath}${finding.path === 'params' ? '' : finding.path.replace(/^params/u, '')}`,
|
|
118
106
|
})));
|
|
119
107
|
}
|
|
120
108
|
});
|
|
121
109
|
}
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
if (!isRecord(
|
|
125
|
-
addFinding(ctx, 'error', 'action_manifest.
|
|
126
|
-
return
|
|
127
|
-
}
|
|
128
|
-
for (const field of Object.keys(manifest)) {
|
|
129
|
-
if (!ACTION_MANIFEST_FIELDS.has(field)) {
|
|
130
|
-
addFinding(ctx, 'error', 'action_manifest.unsupported_field', field, `Recipe action manifests do not support ${field}.`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (manifest.runner_protocol_version !== RECIPE_PROTOCOL_SCHEMA_VERSION) {
|
|
134
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_runner_protocol_version', 'runner_protocol_version', `runner_protocol_version must equal ${RECIPE_PROTOCOL_SCHEMA_VERSION}.`);
|
|
110
|
+
function validateObserver(ctx, observer, index, declaredActions, declaredObservers) {
|
|
111
|
+
const path = `observers[${index}]`;
|
|
112
|
+
if (!isRecord(observer)) {
|
|
113
|
+
addFinding(ctx, 'error', 'action_manifest.invalid_observer', path, `${path} must be an object.`);
|
|
114
|
+
return;
|
|
135
115
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const declaredActions = new Set();
|
|
140
|
-
const officialActions = manifest.supported_official_actions;
|
|
141
|
-
if (!Array.isArray(officialActions) || officialActions.length === 0) {
|
|
142
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_supported_official_actions', 'supported_official_actions', 'supported_official_actions must be a non-empty array.');
|
|
116
|
+
rejectUnknownFields(ctx, observer, OBSERVER_FIELDS, path, 'action_manifest.unsupported_observer_field');
|
|
117
|
+
if (!isNonEmptyString(observer.ref)) {
|
|
118
|
+
addFinding(ctx, 'error', 'action_manifest.invalid_observer_ref', `${path}.ref`, `${path}.ref must be a non-empty string.`);
|
|
143
119
|
}
|
|
144
120
|
else {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (!isNonEmptyString(action)) {
|
|
148
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_supported_action', path, `${path} must be a non-empty string.`);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
if (!OFFICIAL_ACTION_SET.has(action)) {
|
|
152
|
-
addFinding(ctx, 'error', 'action_manifest.unknown_official_action', path, `${action} is not in the Farmslot v1 official action registry.`);
|
|
153
|
-
}
|
|
154
|
-
if (declaredActions.has(action)) {
|
|
155
|
-
addFinding(ctx, 'error', 'action_manifest.duplicate_action', path, `${action} is declared more than once.`);
|
|
156
|
-
}
|
|
157
|
-
declaredActions.add(action);
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
const customActions = manifest.custom_actions;
|
|
161
|
-
if (customActions != null) {
|
|
162
|
-
if (!Array.isArray(customActions)) {
|
|
163
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_custom_actions', 'custom_actions', 'custom_actions must be an array when present.');
|
|
121
|
+
if (!BUILT_IN_UI_OBSERVER_SET.has(observer.ref) && !observer.ref.includes('.')) {
|
|
122
|
+
addFinding(ctx, 'error', 'action_manifest.invalid_observer_ref', `${path}.ref`, `${observer.ref} must be a built-in UI observer ref or a namespaced custom ref.`);
|
|
164
123
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const path = `custom_actions[${index}]`;
|
|
168
|
-
if (!isRecord(action)) {
|
|
169
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_custom_action', path, `${path} must be an object.`);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
if (!isNonEmptyString(action.name)) {
|
|
173
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_custom_action_name', `${path}.name`, `${path}.name must be a non-empty string.`);
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
if (!/^[a-z0-9]+(?:[._-][a-z0-9]+)+$/u.test(action.name)) {
|
|
177
|
-
addFinding(ctx, 'error', 'action_manifest.custom_action_not_namespaced', `${path}.name`, `${action.name} must be a lowercase namespaced action such as wallet.unlock.`);
|
|
178
|
-
}
|
|
179
|
-
if (OFFICIAL_ACTION_SET.has(action.name)) {
|
|
180
|
-
addFinding(ctx, 'error', 'action_manifest.custom_action_overlaps_official', `${path}.name`, `${action.name} is official and must be declared in supported_official_actions.`);
|
|
181
|
-
}
|
|
182
|
-
if (declaredActions.has(action.name)) {
|
|
183
|
-
addFinding(ctx, 'error', 'action_manifest.duplicate_action', `${path}.name`, `${action.name} is declared more than once.`);
|
|
184
|
-
}
|
|
185
|
-
declaredActions.add(action.name);
|
|
186
|
-
}
|
|
187
|
-
validateActionCatalogEntry(ctx, action, path, isNonEmptyString(action.name) ? action.name : undefined);
|
|
188
|
-
if (!Object.hasOwn(action, 'execution_capabilities')) {
|
|
189
|
-
addFinding(ctx, 'error', 'action_manifest.missing_execution_capabilities', `${path}.execution_capabilities`, 'Custom actions must explicitly declare execution_capabilities, including an empty array for read-only actions.');
|
|
190
|
-
}
|
|
191
|
-
});
|
|
124
|
+
if (declaredObservers.has(observer.ref)) {
|
|
125
|
+
addFinding(ctx, 'error', 'action_manifest.duplicate_observer', `${path}.ref`, `${observer.ref} is declared more than once.`);
|
|
192
126
|
}
|
|
127
|
+
declaredObservers.add(observer.ref);
|
|
193
128
|
}
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const declaredOfficialActions = new Set(Array.isArray(officialActions)
|
|
200
|
-
? officialActions.filter((action) => isNonEmptyString(action))
|
|
201
|
-
: []);
|
|
202
|
-
for (const [action, metadata] of Object.entries(manifest.action_metadata)) {
|
|
203
|
-
if (!declaredOfficialActions.has(action)) {
|
|
204
|
-
addFinding(ctx, 'error', 'action_manifest.metadata_for_undeclared_action', `action_metadata.${action}`, `action_metadata may describe only supported_official_actions; declare ${action} once in custom_actions instead.`);
|
|
205
|
-
}
|
|
206
|
-
validateActionCatalogEntry(ctx, metadata, `action_metadata.${action}`, action, action !== 'call' && action !== 'end');
|
|
207
|
-
}
|
|
208
|
-
}
|
|
129
|
+
if (!Array.isArray(observer.default_for) ||
|
|
130
|
+
observer.default_for.length === 0 ||
|
|
131
|
+
new Set(observer.default_for).size !== observer.default_for.length) {
|
|
132
|
+
addFinding(ctx, 'error', 'action_manifest.invalid_observer_default_for', `${path}.default_for`, `${path}.default_for must be a non-empty array of unique declared actions.`);
|
|
133
|
+
return;
|
|
209
134
|
}
|
|
210
|
-
|
|
211
|
-
if (action
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (isCustom)
|
|
217
|
-
continue;
|
|
218
|
-
if (!isRecord(manifest.action_metadata) || !isRecord(manifest.action_metadata[action])) {
|
|
219
|
-
addFinding(ctx, 'error', 'action_manifest.missing_action_metadata', `action_metadata.${action}`, `Official action ${action} must declare metadata and a parameter schema.`);
|
|
135
|
+
observer.default_for.forEach((action, actionIndex) => {
|
|
136
|
+
if (!isNonEmptyString(action) ||
|
|
137
|
+
!declaredActions.has(action) ||
|
|
138
|
+
action === 'call' ||
|
|
139
|
+
action === 'end') {
|
|
140
|
+
addFinding(ctx, 'error', 'action_manifest.invalid_observer_default_for', `${path}.default_for[${actionIndex}]`, `${path}.default_for[${actionIndex}] must reference a declared executable action.`);
|
|
220
141
|
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
export function validateRecipeActionManifestDocument(manifest) {
|
|
145
|
+
const ctx = createContext();
|
|
146
|
+
if (!isRecord(manifest)) {
|
|
147
|
+
addFinding(ctx, 'error', 'action_manifest.invalid_document', '$', 'Recipe action manifest must be a JSON object.');
|
|
148
|
+
return finishResult(ctx);
|
|
221
149
|
}
|
|
222
|
-
|
|
223
|
-
if (
|
|
224
|
-
|
|
225
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_native_bindings', 'native_bindings', 'native_bindings must be an array when present.');
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
nativeBindings.forEach((binding, index) => {
|
|
229
|
-
const path = `native_bindings[${index}]`;
|
|
230
|
-
if (!isRecord(binding)) {
|
|
231
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_native_binding', path, `${path} must be an object.`);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
if (!isNonEmptyString(binding.action)) {
|
|
235
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_native_binding_action', `${path}.action`, `${path}.action must be a non-empty string.`);
|
|
236
|
-
}
|
|
237
|
-
else if (!declaredActions.has(binding.action)) {
|
|
238
|
-
addFinding(ctx, 'error', 'action_manifest.native_binding_for_undeclared_action', `${path}.action`, `Native binding action ${binding.action} is not declared by the manifest.`);
|
|
239
|
-
}
|
|
240
|
-
if (!isNonEmptyString(binding.implementation)) {
|
|
241
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_native_binding_implementation', `${path}.implementation`, `${path}.implementation must be a non-empty string.`);
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
}
|
|
150
|
+
rejectUnknownFields(ctx, manifest, ACTION_MANIFEST_FIELDS, '$', 'action_manifest.unsupported_field');
|
|
151
|
+
if (manifest.$schema !== RECIPE_ACTION_MANIFEST_SCHEMA_URL) {
|
|
152
|
+
addFinding(ctx, 'error', 'action_manifest.invalid_schema_ref', '$schema', `$schema must equal ${RECIPE_ACTION_MANIFEST_SCHEMA_URL}.`);
|
|
245
153
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
154
|
+
if (!isRecord(manifest.actions) || Object.keys(manifest.actions).length === 0) {
|
|
155
|
+
addFinding(ctx, 'error', 'action_manifest.invalid_actions', 'actions', 'actions must be a non-empty object keyed by action name.');
|
|
156
|
+
return finishResult(ctx);
|
|
157
|
+
}
|
|
158
|
+
const declaredActions = new Set();
|
|
159
|
+
for (const [action, entry] of Object.entries(manifest.actions)) {
|
|
160
|
+
const path = `actions.${action}`;
|
|
161
|
+
if (!OFFICIAL_ACTION_SET.has(action) && !/^[a-z0-9]+(?:[._-][a-z0-9]+)+$/u.test(action)) {
|
|
162
|
+
addFinding(ctx, 'error', 'action_manifest.custom_action_not_namespaced', path, `${action} must be an official action or a lowercase namespaced custom action.`);
|
|
250
163
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
if (!isNonEmptyString(capability.capability)) {
|
|
259
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_capability_name', `${path}.capability`, `${path}.capability must be a non-empty string.`);
|
|
260
|
-
}
|
|
261
|
-
if (capability.status !== 'supported' &&
|
|
262
|
-
capability.status !== 'unsupported' &&
|
|
263
|
-
capability.status !== 'partial' &&
|
|
264
|
-
capability.status !== 'planned') {
|
|
265
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_capability_status', `${path}.status`, `${path}.status must be supported, unsupported, partial, or planned.`);
|
|
266
|
-
}
|
|
267
|
-
for (const stringField of ['provider', 'reason']) {
|
|
268
|
-
const value = capability[stringField];
|
|
269
|
-
if (value != null && typeof value !== 'string') {
|
|
270
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_capability_field', `${path}.${stringField}`, `${path}.${stringField} must be a string when present.`);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
for (const arrayField of ['platforms', 'modes', 'artifactTypes']) {
|
|
274
|
-
const value = capability[arrayField];
|
|
275
|
-
if (value != null &&
|
|
276
|
-
(!Array.isArray(value) || value.some((entry) => !isNonEmptyString(entry)))) {
|
|
277
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_capability_field', `${path}.${arrayField}`, `${path}.${arrayField} must be an array of non-empty strings when present.`);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
});
|
|
164
|
+
declaredActions.add(action);
|
|
165
|
+
validateActionCatalogEntry(ctx, action, entry, path);
|
|
166
|
+
if (!OFFICIAL_ACTION_SET.has(action) &&
|
|
167
|
+
(!isRecord(entry) || !Object.hasOwn(entry, 'execution_capabilities'))) {
|
|
168
|
+
addFinding(ctx, 'error', 'action_manifest.missing_execution_capabilities', `${path}.execution_capabilities`, 'Custom actions must explicitly declare execution_capabilities, including an empty array for read-only actions.');
|
|
281
169
|
}
|
|
282
170
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (!Array.isArray(observers)) {
|
|
171
|
+
if (Object.hasOwn(manifest, 'observers')) {
|
|
172
|
+
if (!Array.isArray(manifest.observers)) {
|
|
286
173
|
addFinding(ctx, 'error', 'action_manifest.invalid_observers', 'observers', 'observers must be an array when present.');
|
|
287
174
|
}
|
|
288
175
|
else {
|
|
289
176
|
const declaredObservers = new Set();
|
|
290
|
-
observers.forEach((observer, index) =>
|
|
291
|
-
const path = `observers[${index}]`;
|
|
292
|
-
if (!isRecord(observer)) {
|
|
293
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_observer', path, `${path} must be an object.`);
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
if (!isNonEmptyString(observer.ref)) {
|
|
297
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_observer_ref', `${path}.ref`, `${path}.ref must be a non-empty string.`);
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
if (!BUILT_IN_UI_OBSERVER_SET.has(observer.ref) && !observer.ref.includes('.')) {
|
|
301
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_observer_ref', `${path}.ref`, `${observer.ref} must be a built-in UI observer ref or a namespaced custom ref.`);
|
|
302
|
-
}
|
|
303
|
-
if (declaredObservers.has(observer.ref)) {
|
|
304
|
-
addFinding(ctx, 'error', 'action_manifest.duplicate_observer', `${path}.ref`, `${observer.ref} is declared more than once.`);
|
|
305
|
-
}
|
|
306
|
-
declaredObservers.add(observer.ref);
|
|
307
|
-
}
|
|
308
|
-
if (!isNonEmptyString(observer.description)) {
|
|
309
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_observer_description', `${path}.description`, `${path}.description must be a non-empty string.`);
|
|
310
|
-
}
|
|
311
|
-
if (observer.default_for != null) {
|
|
312
|
-
if (!Array.isArray(observer.default_for)) {
|
|
313
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_observer_default_for', `${path}.default_for`, `${path}.default_for must be an array when present.`);
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
observer.default_for.forEach((action, actionIndex) => {
|
|
317
|
-
if (!isNonEmptyString(action) || !declaredActions.has(action)) {
|
|
318
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_observer_default_for', `${path}.default_for[${actionIndex}]`, `${path}.default_for[${actionIndex}] must reference a declared action.`);
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
for (const field of ['cost', 'redaction']) {
|
|
324
|
-
if (observer[field] != null && typeof observer[field] !== 'string') {
|
|
325
|
-
addFinding(ctx, 'error', 'action_manifest.invalid_observer_field', `${path}.${field}`, `${path}.${field} must be a string when present.`);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
});
|
|
177
|
+
manifest.observers.forEach((observer, index) => validateObserver(ctx, observer, index, declaredActions, declaredObservers));
|
|
329
178
|
}
|
|
330
179
|
}
|
|
331
180
|
return finishResult(ctx);
|