ont-run 0.0.1 → 0.0.3
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/README.md +2 -0
- package/dist/bin/ont.js +113 -15
- package/dist/index.js +181 -25
- package/dist/src/browser/transform.d.ts +1 -0
- package/dist/src/config/categorical.d.ts +59 -0
- package/dist/src/config/index.d.ts +3 -3
- package/dist/src/config/schema.d.ts +9 -0
- package/dist/src/config/types.d.ts +14 -2
- package/dist/src/index.d.ts +2 -2
- package/dist/src/lockfile/types.d.ts +4 -0
- package/dist/src/server/api/middleware.d.ts +7 -1
- package/dist/src/server/mcp/tools.d.ts +3 -3
- package/package.json +2 -2
- package/src/browser/server.ts +27 -1
- package/src/browser/transform.ts +7 -1
- package/src/cli/commands/init.ts +38 -6
- package/src/cli/utils/config-loader.ts +18 -3
- package/src/config/categorical.ts +90 -0
- package/src/config/index.ts +3 -1
- package/src/config/schema.ts +64 -1
- package/src/config/types.ts +15 -2
- package/src/index.ts +2 -1
- package/src/lockfile/differ.ts +9 -1
- package/src/lockfile/hasher.ts +6 -1
- package/src/lockfile/types.ts +4 -0
- package/src/server/api/middleware.ts +18 -4
- package/src/server/api/router.ts +26 -3
- package/src/server/mcp/index.ts +31 -12
- package/src/server/mcp/tools.ts +67 -6
- package/src/server/start.ts +9 -0
package/src/lockfile/differ.ts
CHANGED
|
@@ -114,6 +114,8 @@ export function diffOntology(
|
|
|
114
114
|
const fieldReferencesChanged =
|
|
115
115
|
JSON.stringify(oldFn.fieldReferences) !==
|
|
116
116
|
JSON.stringify(newFn.fieldReferences);
|
|
117
|
+
const userContextChanged =
|
|
118
|
+
!!oldFn.usesUserContext !== !!newFn.usesUserContext;
|
|
117
119
|
|
|
118
120
|
if (
|
|
119
121
|
accessChanged ||
|
|
@@ -121,7 +123,8 @@ export function diffOntology(
|
|
|
121
123
|
inputsChanged ||
|
|
122
124
|
outputsChanged ||
|
|
123
125
|
entitiesChanged ||
|
|
124
|
-
fieldReferencesChanged
|
|
126
|
+
fieldReferencesChanged ||
|
|
127
|
+
userContextChanged
|
|
125
128
|
) {
|
|
126
129
|
functions.push({
|
|
127
130
|
name,
|
|
@@ -136,6 +139,8 @@ export function diffOntology(
|
|
|
136
139
|
oldEntities: entitiesChanged ? oldFn.entities : undefined,
|
|
137
140
|
newEntities: entitiesChanged ? newFn.entities : undefined,
|
|
138
141
|
fieldReferencesChanged: fieldReferencesChanged || undefined,
|
|
142
|
+
userContextChanged: userContextChanged || undefined,
|
|
143
|
+
usesUserContext: userContextChanged ? newFn.usesUserContext : undefined,
|
|
139
144
|
});
|
|
140
145
|
}
|
|
141
146
|
}
|
|
@@ -234,6 +239,9 @@ export function formatDiffForConsole(diff: OntologyDiff): string {
|
|
|
234
239
|
if (fn.fieldReferencesChanged) {
|
|
235
240
|
lines.push(` Field references: changed`);
|
|
236
241
|
}
|
|
242
|
+
if (fn.userContextChanged) {
|
|
243
|
+
lines.push(` User context: ${fn.usesUserContext ? 'added' : 'removed'}`);
|
|
244
|
+
}
|
|
237
245
|
}
|
|
238
246
|
}
|
|
239
247
|
}
|
package/src/lockfile/hasher.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from "crypto";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
4
4
|
import type { OntologyConfig } from "../config/types.js";
|
|
5
|
-
import { getFieldFromMetadata } from "../config/categorical.js";
|
|
5
|
+
import { getFieldFromMetadata, getUserContextFields } from "../config/categorical.js";
|
|
6
6
|
import type {
|
|
7
7
|
OntologySnapshot,
|
|
8
8
|
FunctionShape,
|
|
@@ -112,6 +112,10 @@ export function extractOntology(config: OntologyConfig): OntologySnapshot {
|
|
|
112
112
|
// Extract field references
|
|
113
113
|
const fieldReferences = extractFieldReferences(fn.inputs);
|
|
114
114
|
|
|
115
|
+
// Check if function uses userContext
|
|
116
|
+
const userContextFields = getUserContextFields(fn.inputs);
|
|
117
|
+
const usesUserContext = userContextFields.length > 0;
|
|
118
|
+
|
|
115
119
|
functions[name] = {
|
|
116
120
|
description: fn.description,
|
|
117
121
|
// Sort access groups for consistent hashing
|
|
@@ -122,6 +126,7 @@ export function extractOntology(config: OntologyConfig): OntologySnapshot {
|
|
|
122
126
|
outputsSchema,
|
|
123
127
|
fieldReferences:
|
|
124
128
|
fieldReferences.length > 0 ? fieldReferences : undefined,
|
|
129
|
+
usesUserContext: usesUserContext || undefined,
|
|
125
130
|
};
|
|
126
131
|
}
|
|
127
132
|
|
package/src/lockfile/types.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface FunctionShape {
|
|
|
24
24
|
outputsSchema?: Record<string, unknown>;
|
|
25
25
|
/** Fields that reference other functions for their options */
|
|
26
26
|
fieldReferences?: FieldReference[];
|
|
27
|
+
/** Whether this function uses userContext() for row-level access control */
|
|
28
|
+
usesUserContext?: boolean;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
/**
|
|
@@ -70,6 +72,8 @@ export interface FunctionChange {
|
|
|
70
72
|
oldEntities?: string[];
|
|
71
73
|
newEntities?: string[];
|
|
72
74
|
fieldReferencesChanged?: boolean;
|
|
75
|
+
userContextChanged?: boolean;
|
|
76
|
+
usesUserContext?: boolean;
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
/**
|
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import { createMiddleware } from "hono/factory";
|
|
2
2
|
import type { Context, Next } from "hono";
|
|
3
3
|
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
|
4
|
-
import type { OntologyConfig, ResolverContext, EnvironmentConfig } from "../../config/types.js";
|
|
4
|
+
import type { OntologyConfig, ResolverContext, EnvironmentConfig, AuthResult } from "../../config/types.js";
|
|
5
5
|
import { createLogger } from "../resolver.js";
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Normalize auth function result to AuthResult format.
|
|
9
|
+
* Supports both legacy string[] format and new AuthResult object.
|
|
10
|
+
*/
|
|
11
|
+
export function normalizeAuthResult(result: string[] | AuthResult): AuthResult {
|
|
12
|
+
if (Array.isArray(result)) {
|
|
13
|
+
return { groups: result };
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
/**
|
|
8
19
|
* Context variables added by middleware
|
|
9
20
|
*/
|
|
10
21
|
export interface OntologyVariables {
|
|
11
22
|
resolverContext: ResolverContext;
|
|
12
23
|
accessGroups: string[];
|
|
24
|
+
authResult: AuthResult;
|
|
13
25
|
}
|
|
14
26
|
|
|
15
27
|
/**
|
|
@@ -20,9 +32,11 @@ export function createAuthMiddleware(config: OntologyConfig) {
|
|
|
20
32
|
return createMiddleware<{ Variables: OntologyVariables }>(
|
|
21
33
|
async (c: Context<{ Variables: OntologyVariables }>, next: Next) => {
|
|
22
34
|
try {
|
|
23
|
-
// Call user's auth function
|
|
24
|
-
const
|
|
25
|
-
|
|
35
|
+
// Call user's auth function and normalize result
|
|
36
|
+
const rawResult = await config.auth(c.req.raw);
|
|
37
|
+
const authResult = normalizeAuthResult(rawResult);
|
|
38
|
+
c.set("authResult", authResult);
|
|
39
|
+
c.set("accessGroups", authResult.groups);
|
|
26
40
|
await next();
|
|
27
41
|
} catch (error) {
|
|
28
42
|
return c.json(
|
package/src/server/api/router.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import type { OntologyConfig, FunctionDefinition } from "../../config/types.js";
|
|
3
3
|
import { loadResolver } from "../resolver.js";
|
|
4
|
+
import { getUserContextFields } from "../../config/categorical.js";
|
|
4
5
|
import {
|
|
5
6
|
createAccessControlMiddleware,
|
|
6
7
|
type OntologyVariables,
|
|
@@ -19,6 +20,9 @@ export function createApiRoutes(
|
|
|
19
20
|
for (const [name, fn] of Object.entries(config.functions)) {
|
|
20
21
|
const path = `/${name}`;
|
|
21
22
|
|
|
23
|
+
// Pre-compute userContext fields for this function
|
|
24
|
+
const userContextFields = getUserContextFields(fn.inputs);
|
|
25
|
+
|
|
22
26
|
router.post(
|
|
23
27
|
path,
|
|
24
28
|
// Access control for this specific function
|
|
@@ -26,11 +30,21 @@ export function createApiRoutes(
|
|
|
26
30
|
// Handler
|
|
27
31
|
async (c) => {
|
|
28
32
|
const resolverContext = c.get("resolverContext");
|
|
33
|
+
const authResult = c.get("authResult");
|
|
29
34
|
|
|
30
35
|
// Parse and validate input
|
|
31
36
|
let args: unknown;
|
|
32
37
|
try {
|
|
33
|
-
|
|
38
|
+
let body = await c.req.json();
|
|
39
|
+
|
|
40
|
+
// Inject user context if function requires it
|
|
41
|
+
if (userContextFields.length > 0 && authResult.user) {
|
|
42
|
+
body = { ...body };
|
|
43
|
+
for (const field of userContextFields) {
|
|
44
|
+
(body as Record<string, unknown>)[field] = authResult.user;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
34
48
|
const parsed = fn.inputs.safeParse(body);
|
|
35
49
|
|
|
36
50
|
if (!parsed.success) {
|
|
@@ -45,8 +59,17 @@ export function createApiRoutes(
|
|
|
45
59
|
|
|
46
60
|
args = parsed.data;
|
|
47
61
|
} catch {
|
|
48
|
-
// No body or invalid JSON - try with empty object
|
|
49
|
-
|
|
62
|
+
// No body or invalid JSON - try with empty object (with user context if needed)
|
|
63
|
+
let emptyBody: Record<string, unknown> = {};
|
|
64
|
+
|
|
65
|
+
// Inject user context even for empty body
|
|
66
|
+
if (userContextFields.length > 0 && authResult.user) {
|
|
67
|
+
for (const field of userContextFields) {
|
|
68
|
+
emptyBody[field] = authResult.user;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const parsed = fn.inputs.safeParse(emptyBody);
|
|
50
73
|
if (!parsed.success) {
|
|
51
74
|
return c.json(
|
|
52
75
|
{
|
package/src/server/mcp/index.ts
CHANGED
|
@@ -7,17 +7,33 @@ import {
|
|
|
7
7
|
import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
|
|
8
8
|
import { Hono } from "hono";
|
|
9
9
|
import { cors } from "hono/cors";
|
|
10
|
-
import type { OntologyConfig, ResolverContext, EnvironmentConfig } from "../../config/types.js";
|
|
10
|
+
import type { OntologyConfig, ResolverContext, EnvironmentConfig, AuthResult } from "../../config/types.js";
|
|
11
11
|
import { generateMcpTools, filterToolsByAccess, createToolExecutor } from "./tools.js";
|
|
12
12
|
import { createLogger } from "../resolver.js";
|
|
13
13
|
import { serve } from "../../runtime/index.js";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Normalize auth function result to AuthResult format.
|
|
17
17
|
*/
|
|
18
|
-
function
|
|
19
|
-
if (
|
|
20
|
-
|
|
18
|
+
function normalizeAuthResult(result: string[] | AuthResult): AuthResult {
|
|
19
|
+
if (Array.isArray(result)) {
|
|
20
|
+
return { groups: result };
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Extract AuthResult from AuthInfo
|
|
27
|
+
*/
|
|
28
|
+
function getAuthResult(authInfo?: AuthInfo): AuthResult {
|
|
29
|
+
if (!authInfo?.extra?.authResult) {
|
|
30
|
+
// Fallback to legacy format
|
|
31
|
+
if (authInfo?.extra?.accessGroups) {
|
|
32
|
+
return { groups: authInfo.extra.accessGroups as string[] };
|
|
33
|
+
}
|
|
34
|
+
return { groups: [] };
|
|
35
|
+
}
|
|
36
|
+
return authInfo.extra.authResult as AuthResult;
|
|
21
37
|
}
|
|
22
38
|
|
|
23
39
|
export interface McpServerOptions {
|
|
@@ -68,8 +84,8 @@ export function createMcpServer(options: McpServerOptions): Server {
|
|
|
68
84
|
|
|
69
85
|
// Handle list tools request - filter by per-request access groups
|
|
70
86
|
server.setRequestHandler(ListToolsRequestSchema, async (_request, extra) => {
|
|
71
|
-
const
|
|
72
|
-
const accessibleTools = filterToolsByAccess(allTools,
|
|
87
|
+
const authResult = getAuthResult(extra.authInfo);
|
|
88
|
+
const accessibleTools = filterToolsByAccess(allTools, authResult.groups);
|
|
73
89
|
|
|
74
90
|
return {
|
|
75
91
|
tools: accessibleTools.map((tool) => ({
|
|
@@ -83,10 +99,10 @@ export function createMcpServer(options: McpServerOptions): Server {
|
|
|
83
99
|
// Handle call tool request - validate access per-request
|
|
84
100
|
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
85
101
|
const { name, arguments: args } = request.params;
|
|
86
|
-
const
|
|
102
|
+
const authResult = getAuthResult(extra.authInfo);
|
|
87
103
|
|
|
88
104
|
try {
|
|
89
|
-
const result = await executeToolWithAccess(name, args || {},
|
|
105
|
+
const result = await executeToolWithAccess(name, args || {}, authResult);
|
|
90
106
|
|
|
91
107
|
return {
|
|
92
108
|
content: [
|
|
@@ -146,15 +162,18 @@ export async function startMcpServer(options: McpServerOptions): Promise<{ port:
|
|
|
146
162
|
app.all("/mcp", async (c) => {
|
|
147
163
|
try {
|
|
148
164
|
// Authenticate the request using the config's auth function
|
|
149
|
-
const
|
|
165
|
+
const rawResult = await config.auth(c.req.raw);
|
|
166
|
+
const authResult = normalizeAuthResult(rawResult);
|
|
150
167
|
|
|
151
|
-
// Create AuthInfo object with
|
|
168
|
+
// Create AuthInfo object with full auth result in extra field
|
|
152
169
|
const authInfo: AuthInfo = {
|
|
153
170
|
token: c.req.header("Authorization") || "",
|
|
154
171
|
clientId: "ontology-client",
|
|
155
172
|
scopes: [],
|
|
156
173
|
extra: {
|
|
157
|
-
|
|
174
|
+
authResult,
|
|
175
|
+
// Keep accessGroups for backwards compatibility
|
|
176
|
+
accessGroups: authResult.groups,
|
|
158
177
|
},
|
|
159
178
|
};
|
|
160
179
|
|
package/src/server/mcp/tools.ts
CHANGED
|
@@ -5,8 +5,9 @@ import type {
|
|
|
5
5
|
FunctionDefinition,
|
|
6
6
|
ResolverContext,
|
|
7
7
|
EnvironmentConfig,
|
|
8
|
+
AuthResult,
|
|
8
9
|
} from "../../config/types.js";
|
|
9
|
-
import { getFieldFromMetadata } from "../../config/categorical.js";
|
|
10
|
+
import { getFieldFromMetadata, getUserContextFields, hasUserContextMetadata } from "../../config/categorical.js";
|
|
10
11
|
import { loadResolver, type Logger } from "../resolver.js";
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -32,6 +33,48 @@ export interface McpTool {
|
|
|
32
33
|
fieldReferences?: McpFieldReference[];
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Strip userContext fields from a JSON Schema object.
|
|
38
|
+
* These fields are injected at runtime and should not be exposed to callers.
|
|
39
|
+
*/
|
|
40
|
+
function stripUserContextFromJsonSchema(
|
|
41
|
+
jsonSchema: Record<string, unknown>,
|
|
42
|
+
zodSchema: z.ZodType<unknown>
|
|
43
|
+
): Record<string, unknown> {
|
|
44
|
+
// Only strip from object schemas
|
|
45
|
+
if (jsonSchema.type !== "object" || !jsonSchema.properties) {
|
|
46
|
+
return jsonSchema;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Get userContext field names from the Zod schema
|
|
50
|
+
const userContextFields = getUserContextFields(zodSchema);
|
|
51
|
+
if (userContextFields.length === 0) {
|
|
52
|
+
return jsonSchema;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Create a new schema without userContext fields
|
|
56
|
+
const properties = { ...(jsonSchema.properties as Record<string, unknown>) };
|
|
57
|
+
const required = jsonSchema.required
|
|
58
|
+
? [...(jsonSchema.required as string[])]
|
|
59
|
+
: undefined;
|
|
60
|
+
|
|
61
|
+
for (const field of userContextFields) {
|
|
62
|
+
delete properties[field];
|
|
63
|
+
if (required) {
|
|
64
|
+
const idx = required.indexOf(field);
|
|
65
|
+
if (idx !== -1) {
|
|
66
|
+
required.splice(idx, 1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
...jsonSchema,
|
|
73
|
+
properties,
|
|
74
|
+
required: required && required.length > 0 ? required : undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
35
78
|
/**
|
|
36
79
|
* Recursively extract field references from a Zod schema
|
|
37
80
|
*/
|
|
@@ -101,6 +144,8 @@ export function generateMcpTools(config: OntologyConfig): McpTool[] {
|
|
|
101
144
|
}) as Record<string, unknown>;
|
|
102
145
|
// Remove $schema key if present
|
|
103
146
|
delete inputSchema.$schema;
|
|
147
|
+
// Strip userContext fields - these are injected at runtime
|
|
148
|
+
inputSchema = stripUserContextFromJsonSchema(inputSchema, fn.inputs);
|
|
104
149
|
} catch {
|
|
105
150
|
inputSchema = { type: "object", properties: {} };
|
|
106
151
|
}
|
|
@@ -149,7 +194,7 @@ export function filterToolsByAccess(
|
|
|
149
194
|
}
|
|
150
195
|
|
|
151
196
|
/**
|
|
152
|
-
* Create a tool executor function that accepts per-request
|
|
197
|
+
* Create a tool executor function that accepts per-request auth result
|
|
153
198
|
*/
|
|
154
199
|
export function createToolExecutor(
|
|
155
200
|
config: OntologyConfig,
|
|
@@ -158,7 +203,13 @@ export function createToolExecutor(
|
|
|
158
203
|
envConfig: EnvironmentConfig,
|
|
159
204
|
logger: Logger
|
|
160
205
|
) {
|
|
161
|
-
|
|
206
|
+
// Pre-compute userContext fields for each function
|
|
207
|
+
const userContextFieldsCache = new Map<string, string[]>();
|
|
208
|
+
for (const [name, fn] of Object.entries(config.functions)) {
|
|
209
|
+
userContextFieldsCache.set(name, getUserContextFields(fn.inputs));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return async (toolName: string, args: unknown, authResult: AuthResult): Promise<unknown> => {
|
|
162
213
|
const fn = config.functions[toolName];
|
|
163
214
|
|
|
164
215
|
if (!fn) {
|
|
@@ -167,7 +218,7 @@ export function createToolExecutor(
|
|
|
167
218
|
|
|
168
219
|
// Check access using per-request access groups
|
|
169
220
|
const hasAccess = fn.access.some((group) =>
|
|
170
|
-
|
|
221
|
+
authResult.groups.includes(group)
|
|
171
222
|
);
|
|
172
223
|
|
|
173
224
|
if (!hasAccess) {
|
|
@@ -176,8 +227,18 @@ export function createToolExecutor(
|
|
|
176
227
|
);
|
|
177
228
|
}
|
|
178
229
|
|
|
230
|
+
// Inject user context if function requires it
|
|
231
|
+
const userContextFields = userContextFieldsCache.get(toolName) || [];
|
|
232
|
+
let argsWithContext = args;
|
|
233
|
+
if (userContextFields.length > 0 && authResult.user) {
|
|
234
|
+
argsWithContext = { ...(args as Record<string, unknown>) };
|
|
235
|
+
for (const field of userContextFields) {
|
|
236
|
+
(argsWithContext as Record<string, unknown>)[field] = authResult.user;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
179
240
|
// Validate input
|
|
180
|
-
const parsed = fn.inputs.safeParse(
|
|
241
|
+
const parsed = fn.inputs.safeParse(argsWithContext);
|
|
181
242
|
if (!parsed.success) {
|
|
182
243
|
throw new Error(
|
|
183
244
|
`Invalid input for tool "${toolName}": ${parsed.error.message}`
|
|
@@ -189,7 +250,7 @@ export function createToolExecutor(
|
|
|
189
250
|
env,
|
|
190
251
|
envConfig,
|
|
191
252
|
logger,
|
|
192
|
-
accessGroups,
|
|
253
|
+
accessGroups: authResult.groups,
|
|
193
254
|
};
|
|
194
255
|
|
|
195
256
|
// Load and execute resolver
|
package/src/server/start.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
formatDiffForConsole,
|
|
8
8
|
lockfileExists,
|
|
9
9
|
} from "../lockfile/index.js";
|
|
10
|
+
import { validateUserContextRequirements } from "../config/schema.js";
|
|
10
11
|
import { createApiApp } from "./api/index.js";
|
|
11
12
|
import { startMcpServer } from "./mcp/index.js";
|
|
12
13
|
import { serve, type ServerHandle } from "../runtime/index.js";
|
|
@@ -70,6 +71,14 @@ export async function startOnt(options: StartOntOptions = {}): Promise<StartOntR
|
|
|
70
71
|
consola.info("Loading ontology config...");
|
|
71
72
|
const { config, configDir } = await loadConfig();
|
|
72
73
|
|
|
74
|
+
// Validate userContext requirements
|
|
75
|
+
try {
|
|
76
|
+
await validateUserContextRequirements(config);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
consola.error("User context validation failed:");
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
// Check lockfile
|
|
74
83
|
consola.info("Checking lockfile...");
|
|
75
84
|
const { ontology, hash } = computeOntologyHash(config);
|