@openverb/runtime 2.0.0-alpha.1 → 2.0.0-alpha.2

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.
@@ -0,0 +1,157 @@
1
+ type Actor = {
2
+ type: 'user' | 'service' | 'ai' | 'system';
3
+ id: string;
4
+ roles?: string[];
5
+ };
6
+ type Context = {
7
+ tenantId: string;
8
+ planId: string;
9
+ env: 'dev' | 'staging' | 'prod';
10
+ requestId: string;
11
+ };
12
+ type VerbDefinition = {
13
+ id: string;
14
+ version: string;
15
+ summary: string;
16
+ description?: string;
17
+ inputSchema: any;
18
+ outputSchema: any;
19
+ effects: string[];
20
+ visibility: 'public' | 'internal' | 'admin';
21
+ handler: string;
22
+ policy?: {
23
+ rolesAllowed?: string[];
24
+ plansAllowed?: string[];
25
+ };
26
+ billing?: {
27
+ meterKey?: string;
28
+ defaultDelta?: number;
29
+ };
30
+ };
31
+ type PolicyDecision = {
32
+ decision: 'allow';
33
+ reasons: string[];
34
+ limits?: Record<string, any>;
35
+ meter?: {
36
+ key: string;
37
+ delta: number;
38
+ };
39
+ } | {
40
+ decision: 'deny';
41
+ reasons: string[];
42
+ code: string;
43
+ message: string;
44
+ upsell?: {
45
+ suggestedPlanId: string;
46
+ cta: string;
47
+ };
48
+ };
49
+ type Event = {
50
+ type: string;
51
+ timestamp: string;
52
+ executionId: string;
53
+ data: Record<string, unknown>;
54
+ };
55
+ type Receipt = {
56
+ executionId: string;
57
+ verbId: string;
58
+ verbVersion: string;
59
+ timestamp: string;
60
+ actorId: string;
61
+ tenantId: string;
62
+ status: 'ok' | 'denied' | 'error';
63
+ durationMs: number;
64
+ };
65
+ type HandlerContext = {
66
+ actor: Actor;
67
+ context: Context;
68
+ adapters: Record<string, any>;
69
+ logger: {
70
+ info: (message: string, meta?: any) => void;
71
+ error: (message: string, meta?: any) => void;
72
+ };
73
+ emitEvent: (event: Omit<Event, 'timestamp' | 'executionId'>) => void;
74
+ };
75
+ type Handler<TArgs = any, TResult = any> = (ctx: HandlerContext, args: TArgs) => Promise<TResult>;
76
+ type ExecuteRequest = {
77
+ verbId: string;
78
+ args: unknown;
79
+ actor: Actor;
80
+ context: Context;
81
+ };
82
+ type ExecuteResponse = {
83
+ ok: true;
84
+ result: unknown;
85
+ receipt: Receipt;
86
+ events: Event[];
87
+ uiHints?: Array<{
88
+ type: string;
89
+ level: string;
90
+ message: string;
91
+ }>;
92
+ } | {
93
+ ok: false;
94
+ denied: true;
95
+ reason: {
96
+ code: string;
97
+ message: string;
98
+ };
99
+ upsell?: {
100
+ suggestedPlanId: string;
101
+ cta: string;
102
+ };
103
+ receipt: Receipt;
104
+ events: Event[];
105
+ } | {
106
+ ok: false;
107
+ error: string;
108
+ receipt: Receipt;
109
+ events: Event[];
110
+ };
111
+ type PolicyEngine = (verb: VerbDefinition, actor: Actor, context: Context) => Promise<PolicyDecision>;
112
+ type Runtime = {
113
+ execute: (req: ExecuteRequest) => Promise<ExecuteResponse>;
114
+ introspect: () => VerbDefinition[];
115
+ };
116
+
117
+ type RuntimeConfig = {
118
+ verbs?: VerbDefinition[];
119
+ verbsDir?: string;
120
+ policy: PolicyEngine;
121
+ handlers: Record<string, Handler>;
122
+ adapters?: Record<string, any>;
123
+ onEvent?: (event: Event) => void | Promise<void>;
124
+ onReceipt?: (receipt: Receipt) => void | Promise<void>;
125
+ };
126
+ declare function createRuntime(config: RuntimeConfig): Runtime;
127
+
128
+ declare function loadVerbs(verbsDir?: string): VerbDefinition[];
129
+ declare function getVerbById(verbId: string, verbs?: VerbDefinition[]): VerbDefinition | null;
130
+
131
+ declare function validateInput(schema: any, data: unknown): void;
132
+ declare function validateOutput(schema: any, data: unknown): void;
133
+
134
+ declare class VerbNotFoundError extends Error {
135
+ constructor(verbId: string);
136
+ }
137
+ declare class ValidationError extends Error {
138
+ errors: any[];
139
+ constructor(message: string, errors: any[]);
140
+ }
141
+ declare class PolicyDeniedError extends Error {
142
+ code: string;
143
+ upsell?: {
144
+ suggestedPlanId: string;
145
+ cta: string;
146
+ } | undefined;
147
+ constructor(message: string, code: string, upsell?: {
148
+ suggestedPlanId: string;
149
+ cta: string;
150
+ } | undefined);
151
+ }
152
+ declare class HandlerError extends Error {
153
+ cause?: Error | undefined;
154
+ constructor(message: string, cause?: Error | undefined);
155
+ }
156
+
157
+ export { type Actor, type Context, type Event, type ExecuteRequest, type ExecuteResponse, type Handler, type HandlerContext, HandlerError, type PolicyDecision, PolicyDeniedError, type Receipt, type Runtime, ValidationError, type VerbDefinition, VerbNotFoundError, createRuntime, getVerbById, loadVerbs, validateInput, validateOutput };
@@ -0,0 +1,157 @@
1
+ type Actor = {
2
+ type: 'user' | 'service' | 'ai' | 'system';
3
+ id: string;
4
+ roles?: string[];
5
+ };
6
+ type Context = {
7
+ tenantId: string;
8
+ planId: string;
9
+ env: 'dev' | 'staging' | 'prod';
10
+ requestId: string;
11
+ };
12
+ type VerbDefinition = {
13
+ id: string;
14
+ version: string;
15
+ summary: string;
16
+ description?: string;
17
+ inputSchema: any;
18
+ outputSchema: any;
19
+ effects: string[];
20
+ visibility: 'public' | 'internal' | 'admin';
21
+ handler: string;
22
+ policy?: {
23
+ rolesAllowed?: string[];
24
+ plansAllowed?: string[];
25
+ };
26
+ billing?: {
27
+ meterKey?: string;
28
+ defaultDelta?: number;
29
+ };
30
+ };
31
+ type PolicyDecision = {
32
+ decision: 'allow';
33
+ reasons: string[];
34
+ limits?: Record<string, any>;
35
+ meter?: {
36
+ key: string;
37
+ delta: number;
38
+ };
39
+ } | {
40
+ decision: 'deny';
41
+ reasons: string[];
42
+ code: string;
43
+ message: string;
44
+ upsell?: {
45
+ suggestedPlanId: string;
46
+ cta: string;
47
+ };
48
+ };
49
+ type Event = {
50
+ type: string;
51
+ timestamp: string;
52
+ executionId: string;
53
+ data: Record<string, unknown>;
54
+ };
55
+ type Receipt = {
56
+ executionId: string;
57
+ verbId: string;
58
+ verbVersion: string;
59
+ timestamp: string;
60
+ actorId: string;
61
+ tenantId: string;
62
+ status: 'ok' | 'denied' | 'error';
63
+ durationMs: number;
64
+ };
65
+ type HandlerContext = {
66
+ actor: Actor;
67
+ context: Context;
68
+ adapters: Record<string, any>;
69
+ logger: {
70
+ info: (message: string, meta?: any) => void;
71
+ error: (message: string, meta?: any) => void;
72
+ };
73
+ emitEvent: (event: Omit<Event, 'timestamp' | 'executionId'>) => void;
74
+ };
75
+ type Handler<TArgs = any, TResult = any> = (ctx: HandlerContext, args: TArgs) => Promise<TResult>;
76
+ type ExecuteRequest = {
77
+ verbId: string;
78
+ args: unknown;
79
+ actor: Actor;
80
+ context: Context;
81
+ };
82
+ type ExecuteResponse = {
83
+ ok: true;
84
+ result: unknown;
85
+ receipt: Receipt;
86
+ events: Event[];
87
+ uiHints?: Array<{
88
+ type: string;
89
+ level: string;
90
+ message: string;
91
+ }>;
92
+ } | {
93
+ ok: false;
94
+ denied: true;
95
+ reason: {
96
+ code: string;
97
+ message: string;
98
+ };
99
+ upsell?: {
100
+ suggestedPlanId: string;
101
+ cta: string;
102
+ };
103
+ receipt: Receipt;
104
+ events: Event[];
105
+ } | {
106
+ ok: false;
107
+ error: string;
108
+ receipt: Receipt;
109
+ events: Event[];
110
+ };
111
+ type PolicyEngine = (verb: VerbDefinition, actor: Actor, context: Context) => Promise<PolicyDecision>;
112
+ type Runtime = {
113
+ execute: (req: ExecuteRequest) => Promise<ExecuteResponse>;
114
+ introspect: () => VerbDefinition[];
115
+ };
116
+
117
+ type RuntimeConfig = {
118
+ verbs?: VerbDefinition[];
119
+ verbsDir?: string;
120
+ policy: PolicyEngine;
121
+ handlers: Record<string, Handler>;
122
+ adapters?: Record<string, any>;
123
+ onEvent?: (event: Event) => void | Promise<void>;
124
+ onReceipt?: (receipt: Receipt) => void | Promise<void>;
125
+ };
126
+ declare function createRuntime(config: RuntimeConfig): Runtime;
127
+
128
+ declare function loadVerbs(verbsDir?: string): VerbDefinition[];
129
+ declare function getVerbById(verbId: string, verbs?: VerbDefinition[]): VerbDefinition | null;
130
+
131
+ declare function validateInput(schema: any, data: unknown): void;
132
+ declare function validateOutput(schema: any, data: unknown): void;
133
+
134
+ declare class VerbNotFoundError extends Error {
135
+ constructor(verbId: string);
136
+ }
137
+ declare class ValidationError extends Error {
138
+ errors: any[];
139
+ constructor(message: string, errors: any[]);
140
+ }
141
+ declare class PolicyDeniedError extends Error {
142
+ code: string;
143
+ upsell?: {
144
+ suggestedPlanId: string;
145
+ cta: string;
146
+ } | undefined;
147
+ constructor(message: string, code: string, upsell?: {
148
+ suggestedPlanId: string;
149
+ cta: string;
150
+ } | undefined);
151
+ }
152
+ declare class HandlerError extends Error {
153
+ cause?: Error | undefined;
154
+ constructor(message: string, cause?: Error | undefined);
155
+ }
156
+
157
+ export { type Actor, type Context, type Event, type ExecuteRequest, type ExecuteResponse, type Handler, type HandlerContext, HandlerError, type PolicyDecision, PolicyDeniedError, type Receipt, type Runtime, ValidationError, type VerbDefinition, VerbNotFoundError, createRuntime, getVerbById, loadVerbs, validateInput, validateOutput };
package/dist/index.js ADDED
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ HandlerError: () => HandlerError,
34
+ PolicyDeniedError: () => PolicyDeniedError,
35
+ ValidationError: () => ValidationError,
36
+ VerbNotFoundError: () => VerbNotFoundError,
37
+ createRuntime: () => createRuntime,
38
+ getVerbById: () => getVerbById,
39
+ loadVerbs: () => loadVerbs,
40
+ validateInput: () => validateInput,
41
+ validateOutput: () => validateOutput
42
+ });
43
+ module.exports = __toCommonJS(index_exports);
44
+
45
+ // src/engine.ts
46
+ var import_node_crypto = __toESM(require("crypto"));
47
+
48
+ // src/registry.ts
49
+ var fs = __toESM(require("fs"));
50
+ var path = __toESM(require("path"));
51
+ var cachedVerbs = null;
52
+ function loadVerbs(verbsDir = "./openverb/verbs") {
53
+ if (cachedVerbs) return cachedVerbs;
54
+ const resolvedDir = path.resolve(process.cwd(), verbsDir);
55
+ if (!fs.existsSync(resolvedDir)) {
56
+ console.warn(`Verbs directory not found: ${resolvedDir}`);
57
+ return [];
58
+ }
59
+ const files = fs.readdirSync(resolvedDir).filter((f) => f.endsWith(".json"));
60
+ cachedVerbs = files.map((file) => {
61
+ const content = fs.readFileSync(path.join(resolvedDir, file), "utf-8");
62
+ return JSON.parse(content);
63
+ });
64
+ return cachedVerbs;
65
+ }
66
+ function getVerbById(verbId, verbs) {
67
+ const registry = verbs || loadVerbs();
68
+ return registry.find((v) => v.id === verbId) || null;
69
+ }
70
+
71
+ // src/validator.ts
72
+ var import_ajv = __toESM(require("ajv"));
73
+
74
+ // src/errors.ts
75
+ var VerbNotFoundError = class extends Error {
76
+ constructor(verbId) {
77
+ super(`Verb not found: ${verbId}`);
78
+ this.name = "VerbNotFoundError";
79
+ }
80
+ };
81
+ var ValidationError = class extends Error {
82
+ constructor(message, errors) {
83
+ super(message);
84
+ this.errors = errors;
85
+ this.name = "ValidationError";
86
+ }
87
+ };
88
+ var PolicyDeniedError = class extends Error {
89
+ constructor(message, code, upsell) {
90
+ super(message);
91
+ this.code = code;
92
+ this.upsell = upsell;
93
+ this.name = "PolicyDeniedError";
94
+ }
95
+ };
96
+ var HandlerError = class extends Error {
97
+ constructor(message, cause) {
98
+ super(message);
99
+ this.cause = cause;
100
+ this.name = "HandlerError";
101
+ }
102
+ };
103
+
104
+ // src/validator.ts
105
+ var ajv = new import_ajv.default({ allErrors: true });
106
+ function validateInput(schema, data) {
107
+ const validate = ajv.compile(schema);
108
+ const valid = validate(data);
109
+ if (!valid) {
110
+ throw new ValidationError(
111
+ "Input validation failed",
112
+ validate.errors || []
113
+ );
114
+ }
115
+ }
116
+ function validateOutput(schema, data) {
117
+ const validate = ajv.compile(schema);
118
+ const valid = validate(data);
119
+ if (!valid) {
120
+ console.error("Output validation failed:", validate.errors);
121
+ }
122
+ }
123
+
124
+ // src/engine.ts
125
+ function createRuntime(config) {
126
+ const verbs = config.verbs || loadVerbs(config.verbsDir);
127
+ const events = [];
128
+ const executionId = import_node_crypto.default.randomUUID();
129
+ async function execute(req) {
130
+ const startTime = Date.now();
131
+ const verb = getVerbById(req.verbId, verbs);
132
+ if (!verb) {
133
+ const receipt = createReceipt(req, "error", startTime);
134
+ return {
135
+ ok: false,
136
+ error: `Verb not found: ${req.verbId}`,
137
+ receipt,
138
+ events: []
139
+ };
140
+ }
141
+ try {
142
+ validateInput(verb.inputSchema, req.args);
143
+ const decision = await config.policy(verb, req.actor, req.context);
144
+ emitEvent({
145
+ type: decision.decision === "allow" ? "policy.allowed" : "policy.denied",
146
+ data: { verbId: verb.id, decision }
147
+ });
148
+ if (decision.decision === "deny") {
149
+ const receipt2 = createReceipt(req, "denied", startTime);
150
+ await config.onReceipt?.(receipt2);
151
+ return {
152
+ ok: false,
153
+ denied: true,
154
+ reason: { code: decision.code, message: decision.message },
155
+ upsell: decision.upsell,
156
+ receipt: receipt2,
157
+ events
158
+ };
159
+ }
160
+ const handler = config.handlers[verb.handler];
161
+ if (!handler) {
162
+ throw new HandlerError(`Handler not found: ${verb.handler}`);
163
+ }
164
+ const ctx = {
165
+ actor: req.actor,
166
+ context: req.context,
167
+ adapters: config.adapters || {},
168
+ logger: {
169
+ info: (msg, meta) => console.log(`[INFO] ${msg}`, meta),
170
+ error: (msg, meta) => console.error(`[ERROR] ${msg}`, meta)
171
+ },
172
+ emitEvent
173
+ };
174
+ const result = await handler(ctx, req.args);
175
+ validateOutput(verb.outputSchema, result);
176
+ const receipt = createReceipt(req, "ok", startTime);
177
+ await config.onReceipt?.(receipt);
178
+ return {
179
+ ok: true,
180
+ result,
181
+ receipt,
182
+ events
183
+ };
184
+ } catch (error) {
185
+ emitEvent({
186
+ type: "handler.error",
187
+ data: { error: error instanceof Error ? error.message : "Unknown error" }
188
+ });
189
+ const receipt = createReceipt(req, "error", startTime);
190
+ await config.onReceipt?.(receipt);
191
+ return {
192
+ ok: false,
193
+ error: error instanceof Error ? error.message : "Unknown error",
194
+ receipt,
195
+ events
196
+ };
197
+ }
198
+ }
199
+ function emitEvent(event) {
200
+ const fullEvent = {
201
+ ...event,
202
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
203
+ executionId
204
+ };
205
+ events.push(fullEvent);
206
+ config.onEvent?.(fullEvent);
207
+ }
208
+ function createReceipt(req, status, startTime) {
209
+ return {
210
+ executionId,
211
+ verbId: req.verbId,
212
+ verbVersion: getVerbById(req.verbId, verbs)?.version || "0.0.0",
213
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
214
+ actorId: req.actor.id,
215
+ tenantId: req.context.tenantId,
216
+ status,
217
+ durationMs: Date.now() - startTime
218
+ };
219
+ }
220
+ function introspect() {
221
+ return verbs.map((v) => ({
222
+ id: v.id,
223
+ version: v.version,
224
+ summary: v.summary,
225
+ inputSchema: v.inputSchema,
226
+ outputSchema: v.outputSchema,
227
+ effects: v.effects,
228
+ visibility: v.visibility,
229
+ handler: v.handler
230
+ }));
231
+ }
232
+ return { execute, introspect };
233
+ }
234
+ // Annotate the CommonJS export names for ESM import in node:
235
+ 0 && (module.exports = {
236
+ HandlerError,
237
+ PolicyDeniedError,
238
+ ValidationError,
239
+ VerbNotFoundError,
240
+ createRuntime,
241
+ getVerbById,
242
+ loadVerbs,
243
+ validateInput,
244
+ validateOutput
245
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,200 @@
1
+ // src/engine.ts
2
+ import crypto from "crypto";
3
+
4
+ // src/registry.ts
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ var cachedVerbs = null;
8
+ function loadVerbs(verbsDir = "./openverb/verbs") {
9
+ if (cachedVerbs) return cachedVerbs;
10
+ const resolvedDir = path.resolve(process.cwd(), verbsDir);
11
+ if (!fs.existsSync(resolvedDir)) {
12
+ console.warn(`Verbs directory not found: ${resolvedDir}`);
13
+ return [];
14
+ }
15
+ const files = fs.readdirSync(resolvedDir).filter((f) => f.endsWith(".json"));
16
+ cachedVerbs = files.map((file) => {
17
+ const content = fs.readFileSync(path.join(resolvedDir, file), "utf-8");
18
+ return JSON.parse(content);
19
+ });
20
+ return cachedVerbs;
21
+ }
22
+ function getVerbById(verbId, verbs) {
23
+ const registry = verbs || loadVerbs();
24
+ return registry.find((v) => v.id === verbId) || null;
25
+ }
26
+
27
+ // src/validator.ts
28
+ import Ajv from "ajv";
29
+
30
+ // src/errors.ts
31
+ var VerbNotFoundError = class extends Error {
32
+ constructor(verbId) {
33
+ super(`Verb not found: ${verbId}`);
34
+ this.name = "VerbNotFoundError";
35
+ }
36
+ };
37
+ var ValidationError = class extends Error {
38
+ constructor(message, errors) {
39
+ super(message);
40
+ this.errors = errors;
41
+ this.name = "ValidationError";
42
+ }
43
+ };
44
+ var PolicyDeniedError = class extends Error {
45
+ constructor(message, code, upsell) {
46
+ super(message);
47
+ this.code = code;
48
+ this.upsell = upsell;
49
+ this.name = "PolicyDeniedError";
50
+ }
51
+ };
52
+ var HandlerError = class extends Error {
53
+ constructor(message, cause) {
54
+ super(message);
55
+ this.cause = cause;
56
+ this.name = "HandlerError";
57
+ }
58
+ };
59
+
60
+ // src/validator.ts
61
+ var ajv = new Ajv({ allErrors: true });
62
+ function validateInput(schema, data) {
63
+ const validate = ajv.compile(schema);
64
+ const valid = validate(data);
65
+ if (!valid) {
66
+ throw new ValidationError(
67
+ "Input validation failed",
68
+ validate.errors || []
69
+ );
70
+ }
71
+ }
72
+ function validateOutput(schema, data) {
73
+ const validate = ajv.compile(schema);
74
+ const valid = validate(data);
75
+ if (!valid) {
76
+ console.error("Output validation failed:", validate.errors);
77
+ }
78
+ }
79
+
80
+ // src/engine.ts
81
+ function createRuntime(config) {
82
+ const verbs = config.verbs || loadVerbs(config.verbsDir);
83
+ const events = [];
84
+ const executionId = crypto.randomUUID();
85
+ async function execute(req) {
86
+ const startTime = Date.now();
87
+ const verb = getVerbById(req.verbId, verbs);
88
+ if (!verb) {
89
+ const receipt = createReceipt(req, "error", startTime);
90
+ return {
91
+ ok: false,
92
+ error: `Verb not found: ${req.verbId}`,
93
+ receipt,
94
+ events: []
95
+ };
96
+ }
97
+ try {
98
+ validateInput(verb.inputSchema, req.args);
99
+ const decision = await config.policy(verb, req.actor, req.context);
100
+ emitEvent({
101
+ type: decision.decision === "allow" ? "policy.allowed" : "policy.denied",
102
+ data: { verbId: verb.id, decision }
103
+ });
104
+ if (decision.decision === "deny") {
105
+ const receipt2 = createReceipt(req, "denied", startTime);
106
+ await config.onReceipt?.(receipt2);
107
+ return {
108
+ ok: false,
109
+ denied: true,
110
+ reason: { code: decision.code, message: decision.message },
111
+ upsell: decision.upsell,
112
+ receipt: receipt2,
113
+ events
114
+ };
115
+ }
116
+ const handler = config.handlers[verb.handler];
117
+ if (!handler) {
118
+ throw new HandlerError(`Handler not found: ${verb.handler}`);
119
+ }
120
+ const ctx = {
121
+ actor: req.actor,
122
+ context: req.context,
123
+ adapters: config.adapters || {},
124
+ logger: {
125
+ info: (msg, meta) => console.log(`[INFO] ${msg}`, meta),
126
+ error: (msg, meta) => console.error(`[ERROR] ${msg}`, meta)
127
+ },
128
+ emitEvent
129
+ };
130
+ const result = await handler(ctx, req.args);
131
+ validateOutput(verb.outputSchema, result);
132
+ const receipt = createReceipt(req, "ok", startTime);
133
+ await config.onReceipt?.(receipt);
134
+ return {
135
+ ok: true,
136
+ result,
137
+ receipt,
138
+ events
139
+ };
140
+ } catch (error) {
141
+ emitEvent({
142
+ type: "handler.error",
143
+ data: { error: error instanceof Error ? error.message : "Unknown error" }
144
+ });
145
+ const receipt = createReceipt(req, "error", startTime);
146
+ await config.onReceipt?.(receipt);
147
+ return {
148
+ ok: false,
149
+ error: error instanceof Error ? error.message : "Unknown error",
150
+ receipt,
151
+ events
152
+ };
153
+ }
154
+ }
155
+ function emitEvent(event) {
156
+ const fullEvent = {
157
+ ...event,
158
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
159
+ executionId
160
+ };
161
+ events.push(fullEvent);
162
+ config.onEvent?.(fullEvent);
163
+ }
164
+ function createReceipt(req, status, startTime) {
165
+ return {
166
+ executionId,
167
+ verbId: req.verbId,
168
+ verbVersion: getVerbById(req.verbId, verbs)?.version || "0.0.0",
169
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
170
+ actorId: req.actor.id,
171
+ tenantId: req.context.tenantId,
172
+ status,
173
+ durationMs: Date.now() - startTime
174
+ };
175
+ }
176
+ function introspect() {
177
+ return verbs.map((v) => ({
178
+ id: v.id,
179
+ version: v.version,
180
+ summary: v.summary,
181
+ inputSchema: v.inputSchema,
182
+ outputSchema: v.outputSchema,
183
+ effects: v.effects,
184
+ visibility: v.visibility,
185
+ handler: v.handler
186
+ }));
187
+ }
188
+ return { execute, introspect };
189
+ }
190
+ export {
191
+ HandlerError,
192
+ PolicyDeniedError,
193
+ ValidationError,
194
+ VerbNotFoundError,
195
+ createRuntime,
196
+ getVerbById,
197
+ loadVerbs,
198
+ validateInput,
199
+ validateOutput
200
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openverb/runtime",
3
- "version": "2.0.0-alpha.1",
3
+ "version": "2.0.0-alpha.2",
4
4
  "description": "OpenVerb execution runtime",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -12,6 +12,9 @@
12
12
  "import": "./dist/index.mjs"
13
13
  }
14
14
  },
15
+ "files": [
16
+ "dist"
17
+ ],
15
18
  "scripts": {
16
19
  "build": "tsup src/index.ts --format cjs,esm --dts",
17
20
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
package/src/engine.ts DELETED
@@ -1,166 +0,0 @@
1
- import crypto from 'node:crypto'
2
- import { loadVerbs, getVerbById } from './registry'
3
- import { validateInput, validateOutput } from './validator'
4
- import { VerbNotFoundError, PolicyDeniedError, HandlerError } from './errors'
5
- import type {
6
- Runtime,
7
- ExecuteRequest,
8
- ExecuteResponse,
9
- VerbDefinition,
10
- PolicyEngine,
11
- Handler,
12
- Event,
13
- Receipt,
14
- HandlerContext
15
- } from './types'
16
-
17
- type RuntimeConfig = {
18
- verbs?: VerbDefinition[]
19
- verbsDir?: string
20
- policy: PolicyEngine
21
- handlers: Record<string, Handler>
22
- adapters?: Record<string, any>
23
- onEvent?: (event: Event) => void | Promise<void>
24
- onReceipt?: (receipt: Receipt) => void | Promise<void>
25
- }
26
-
27
- export function createRuntime(config: RuntimeConfig): Runtime {
28
- const verbs = config.verbs || loadVerbs(config.verbsDir)
29
- const events: Event[] = []
30
- const executionId = crypto.randomUUID()
31
-
32
- async function execute(req: ExecuteRequest): Promise<ExecuteResponse> {
33
- const startTime = Date.now()
34
-
35
- // Stage 1: Resolve verb
36
- const verb = getVerbById(req.verbId, verbs)
37
- if (!verb) {
38
- const receipt = createReceipt(req, 'error', startTime)
39
- return {
40
- ok: false,
41
- error: `Verb not found: ${req.verbId}`,
42
- receipt,
43
- events: []
44
- }
45
- }
46
-
47
- try {
48
- // Stage 2: Validate input
49
- validateInput(verb.inputSchema, req.args)
50
-
51
- // Stage 3: Policy check
52
- const decision = await config.policy(verb, req.actor, req.context)
53
-
54
- emitEvent({
55
- type: decision.decision === 'allow' ? 'policy.allowed' : 'policy.denied',
56
- data: { verbId: verb.id, decision }
57
- })
58
-
59
- if (decision.decision === 'deny') {
60
- const receipt = createReceipt(req, 'denied', startTime)
61
- await config.onReceipt?.(receipt)
62
-
63
- return {
64
- ok: false,
65
- denied: true,
66
- reason: { code: decision.code, message: decision.message },
67
- upsell: decision.upsell,
68
- receipt,
69
- events
70
- }
71
- }
72
-
73
- // Stage 4: Execute handler
74
- const handler = config.handlers[verb.handler]
75
- if (!handler) {
76
- throw new HandlerError(`Handler not found: ${verb.handler}`)
77
- }
78
-
79
- const ctx: HandlerContext = {
80
- actor: req.actor,
81
- context: req.context,
82
- adapters: config.adapters || {},
83
- logger: {
84
- info: (msg, meta) => console.log(`[INFO] ${msg}`, meta),
85
- error: (msg, meta) => console.error(`[ERROR] ${msg}`, meta)
86
- },
87
- emitEvent
88
- }
89
-
90
- const result = await handler(ctx, req.args)
91
-
92
- // Stage 5: Validate output
93
- validateOutput(verb.outputSchema, result)
94
-
95
- // Stage 6: Create receipt
96
- const receipt = createReceipt(req, 'ok', startTime)
97
- await config.onReceipt?.(receipt)
98
-
99
- // Stage 7: Return response
100
- return {
101
- ok: true,
102
- result,
103
- receipt,
104
- events
105
- }
106
-
107
- } catch (error) {
108
- emitEvent({
109
- type: 'handler.error',
110
- data: { error: error instanceof Error ? error.message : 'Unknown error' }
111
- })
112
-
113
- const receipt = createReceipt(req, 'error', startTime)
114
- await config.onReceipt?.(receipt)
115
-
116
- return {
117
- ok: false,
118
- error: error instanceof Error ? error.message : 'Unknown error',
119
- receipt,
120
- events
121
- }
122
- }
123
- }
124
-
125
- function emitEvent(event: Omit<Event, 'timestamp' | 'executionId'>) {
126
- const fullEvent: Event = {
127
- ...event,
128
- timestamp: new Date().toISOString(),
129
- executionId
130
- }
131
- events.push(fullEvent)
132
- config.onEvent?.(fullEvent)
133
- }
134
-
135
- function createReceipt(
136
- req: ExecuteRequest,
137
- status: 'ok' | 'denied' | 'error',
138
- startTime: number
139
- ): Receipt {
140
- return {
141
- executionId,
142
- verbId: req.verbId,
143
- verbVersion: getVerbById(req.verbId, verbs)?.version || '0.0.0',
144
- timestamp: new Date().toISOString(),
145
- actorId: req.actor.id,
146
- tenantId: req.context.tenantId,
147
- status,
148
- durationMs: Date.now() - startTime
149
- }
150
- }
151
-
152
- function introspect(): VerbDefinition[] {
153
- return verbs.map(v => ({
154
- id: v.id,
155
- version: v.version,
156
- summary: v.summary,
157
- inputSchema: v.inputSchema,
158
- outputSchema: v.outputSchema,
159
- effects: v.effects,
160
- visibility: v.visibility,
161
- handler: v.handler
162
- }))
163
- }
164
-
165
- return { execute, introspect }
166
- }
package/src/errors.ts DELETED
@@ -1,31 +0,0 @@
1
- export class VerbNotFoundError extends Error {
2
- constructor(verbId: string) {
3
- super(`Verb not found: ${verbId}`)
4
- this.name = 'VerbNotFoundError'
5
- }
6
- }
7
-
8
- export class ValidationError extends Error {
9
- constructor(message: string, public errors: any[]) {
10
- super(message)
11
- this.name = 'ValidationError'
12
- }
13
- }
14
-
15
- export class PolicyDeniedError extends Error {
16
- constructor(
17
- message: string,
18
- public code: string,
19
- public upsell?: { suggestedPlanId: string; cta: string }
20
- ) {
21
- super(message)
22
- this.name = 'PolicyDeniedError'
23
- }
24
- }
25
-
26
- export class HandlerError extends Error {
27
- constructor(message: string, public cause?: Error) {
28
- super(message)
29
- this.name = 'HandlerError'
30
- }
31
- }
package/src/index.ts DELETED
@@ -1,18 +0,0 @@
1
- // @openverb/runtime - Main entry point
2
- export { createRuntime } from './engine'
3
- export { loadVerbs, getVerbById } from './registry'
4
- export { validateInput, validateOutput } from './validator'
5
- export type {
6
- Runtime,
7
- ExecuteRequest,
8
- ExecuteResponse,
9
- VerbDefinition,
10
- HandlerContext,
11
- Handler,
12
- Actor,
13
- Context,
14
- Receipt,
15
- Event,
16
- PolicyDecision
17
- } from './types'
18
- export { VerbNotFoundError, ValidationError, PolicyDeniedError, HandlerError } from './errors'
package/src/registry.ts DELETED
@@ -1,34 +0,0 @@
1
- import * as fs from 'node:fs'
2
- import * as path from 'node:path'
3
- import type { VerbDefinition } from './types'
4
-
5
- let cachedVerbs: VerbDefinition[] | null = null
6
-
7
- export function loadVerbs(verbsDir: string = './openverb/verbs'): VerbDefinition[] {
8
- if (cachedVerbs) return cachedVerbs
9
-
10
- const resolvedDir = path.resolve(process.cwd(), verbsDir)
11
-
12
- if (!fs.existsSync(resolvedDir)) {
13
- console.warn(`Verbs directory not found: ${resolvedDir}`)
14
- return []
15
- }
16
-
17
- const files = fs.readdirSync(resolvedDir).filter(f => f.endsWith('.json'))
18
-
19
- cachedVerbs = files.map(file => {
20
- const content = fs.readFileSync(path.join(resolvedDir, file), 'utf-8')
21
- return JSON.parse(content) as VerbDefinition
22
- })
23
-
24
- return cachedVerbs
25
- }
26
-
27
- export function getVerbById(verbId: string, verbs?: VerbDefinition[]): VerbDefinition | null {
28
- const registry = verbs || loadVerbs()
29
- return registry.find(v => v.id === verbId) || null
30
- }
31
-
32
- export function clearCache() {
33
- cachedVerbs = null
34
- }
package/src/types.ts DELETED
@@ -1,128 +0,0 @@
1
- import type { JSONSchemaType } from 'ajv'
2
-
3
- // Core types
4
- export type Actor = {
5
- type: 'user' | 'service' | 'ai' | 'system'
6
- id: string
7
- roles?: string[]
8
- }
9
-
10
- export type Context = {
11
- tenantId: string
12
- planId: string
13
- env: 'dev' | 'staging' | 'prod'
14
- requestId: string
15
- }
16
-
17
- export type VerbDefinition = {
18
- id: string
19
- version: string
20
- summary: string
21
- description?: string
22
- inputSchema: any // JSON Schema
23
- outputSchema: any // JSON Schema
24
- effects: string[]
25
- visibility: 'public' | 'internal' | 'admin'
26
- handler: string
27
- policy?: {
28
- rolesAllowed?: string[]
29
- plansAllowed?: string[]
30
- }
31
- billing?: {
32
- meterKey?: string
33
- defaultDelta?: number
34
- }
35
- }
36
-
37
- export type PolicyDecision =
38
- | {
39
- decision: 'allow'
40
- reasons: string[]
41
- limits?: Record<string, any>
42
- meter?: { key: string; delta: number }
43
- }
44
- | {
45
- decision: 'deny'
46
- reasons: string[]
47
- code: string
48
- message: string
49
- upsell?: {
50
- suggestedPlanId: string
51
- cta: string
52
- }
53
- }
54
-
55
- export type Event = {
56
- type: string
57
- timestamp: string
58
- executionId: string
59
- data: Record<string, unknown>
60
- }
61
-
62
- export type Receipt = {
63
- executionId: string
64
- verbId: string
65
- verbVersion: string
66
- timestamp: string
67
- actorId: string
68
- tenantId: string
69
- status: 'ok' | 'denied' | 'error'
70
- durationMs: number
71
- }
72
-
73
- export type HandlerContext = {
74
- actor: Actor
75
- context: Context
76
- adapters: Record<string, any>
77
- logger: {
78
- info: (message: string, meta?: any) => void
79
- error: (message: string, meta?: any) => void
80
- }
81
- emitEvent: (event: Omit<Event, 'timestamp' | 'executionId'>) => void
82
- }
83
-
84
- export type Handler<TArgs = any, TResult = any> = (
85
- ctx: HandlerContext,
86
- args: TArgs
87
- ) => Promise<TResult>
88
-
89
- export type ExecuteRequest = {
90
- verbId: string
91
- args: unknown
92
- actor: Actor
93
- context: Context
94
- }
95
-
96
- export type ExecuteResponse =
97
- | {
98
- ok: true
99
- result: unknown
100
- receipt: Receipt
101
- events: Event[]
102
- uiHints?: Array<{ type: string; level: string; message: string }>
103
- }
104
- | {
105
- ok: false
106
- denied: true
107
- reason: { code: string; message: string }
108
- upsell?: { suggestedPlanId: string; cta: string }
109
- receipt: Receipt
110
- events: Event[]
111
- }
112
- | {
113
- ok: false
114
- error: string
115
- receipt: Receipt
116
- events: Event[]
117
- }
118
-
119
- export type PolicyEngine = (
120
- verb: VerbDefinition,
121
- actor: Actor,
122
- context: Context
123
- ) => Promise<PolicyDecision>
124
-
125
- export type Runtime = {
126
- execute: (req: ExecuteRequest) => Promise<ExecuteResponse>
127
- introspect: () => VerbDefinition[]
128
- }
package/src/validator.ts DELETED
@@ -1,26 +0,0 @@
1
- import Ajv from 'ajv'
2
- import { ValidationError } from './errors'
3
-
4
- const ajv = new Ajv({ allErrors: true })
5
-
6
- export function validateInput(schema: any, data: unknown): void {
7
- const validate = ajv.compile(schema)
8
- const valid = validate(data)
9
-
10
- if (!valid) {
11
- throw new ValidationError(
12
- 'Input validation failed',
13
- validate.errors || []
14
- )
15
- }
16
- }
17
-
18
- export function validateOutput(schema: any, data: unknown): void {
19
- const validate = ajv.compile(schema)
20
- const valid = validate(data)
21
-
22
- if (!valid) {
23
- console.error('Output validation failed:', validate.errors)
24
- // Don't throw - log warning instead
25
- }
26
- }
package/tsconfig.json DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "ESNext",
5
- "lib": ["ES2020"],
6
- "moduleResolution": "node",
7
- "resolveJsonModule": true,
8
- "declaration": true,
9
- "declarationMap": true,
10
- "outDir": "dist",
11
- "strict": true,
12
- "esModuleInterop": true,
13
- "skipLibCheck": true,
14
- "forceConsistentCasingInFileNames": true
15
- },
16
- "include": ["src/**/*"],
17
- "exclude": ["node_modules", "dist"]
18
- }