@ory/argus 0.9.0 → 0.10.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/README.md +3 -3
- package/assets/skills/auth-setup/SKILL.md +209 -49
- package/assets/skills/login-flow/SKILL.md +192 -17
- package/assets/skills/social-login/SKILL.md +9 -0
- package/dist/adapters.d.ts +104 -0
- package/dist/adapters.js +201 -0
- package/dist/agent-auth.js +13 -0
- package/dist/contract-suite.d.ts +87 -0
- package/dist/contract-suite.js +239 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +8 -1
- package/dist/lifecycle.js +129 -0
- package/dist/local/configs.d.ts +2 -2
- package/dist/local/configs.js +10 -1
- package/dist/local/manager.d.ts +6 -4
- package/dist/local/manager.js +91 -16
- package/dist/permissions.d.ts +15 -3
- package/dist/permissions.js +17 -2
- package/dist/testing.d.ts +214 -0
- package/dist/testing.js +372 -0
- package/dist/tool-catalog.d.ts +6 -0
- package/dist/tool-catalog.js +54 -0
- package/package.json +1 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test utilities for Ory agent plugin integration tests.
|
|
3
|
+
*
|
|
4
|
+
* These helpers provide mock Ory API responses, error factories, and
|
|
5
|
+
* common assertion patterns so that harness plugin tests focus on
|
|
6
|
+
* harness-specific behavior rather than duplicating boilerplate.
|
|
7
|
+
*/
|
|
8
|
+
import { vi } from "vitest";
|
|
9
|
+
import { OryAgentClient } from "./client.js";
|
|
10
|
+
import type { TraceSpan, TraceEvent } from "./tracer.js";
|
|
11
|
+
export { runHarnessContractSuite, type HarnessContractAdapter, type ContractContext, type ContractGates, type ContractOutcome, } from "./contract-suite.js";
|
|
12
|
+
/**
|
|
13
|
+
* Create an OryAgentClient with session caching disabled.
|
|
14
|
+
* Pass overrides to customize (e.g. a different harness name).
|
|
15
|
+
*/
|
|
16
|
+
export declare function createMockClient(overrides?: Partial<ConstructorParameters<typeof OryAgentClient>[0]>): OryAgentClient;
|
|
17
|
+
/**
|
|
18
|
+
* Stub an internal API instance method on the client.
|
|
19
|
+
* Returns a vi.fn mock so callers can assert on calls.
|
|
20
|
+
*/
|
|
21
|
+
export declare function stubApi<K extends "frontend" | "oauth2" | "permission" | "relationship">(client: OryAgentClient, api: K, method: string, impl: (...args: unknown[]) => unknown): ReturnType<typeof vi.fn>;
|
|
22
|
+
/** Successful session verification response (wraps Ory API shape). */
|
|
23
|
+
export declare const MOCK_SESSION_RESPONSE: {
|
|
24
|
+
data: {
|
|
25
|
+
id: string;
|
|
26
|
+
active: boolean;
|
|
27
|
+
authenticated_at: string;
|
|
28
|
+
expires_at: string;
|
|
29
|
+
authenticator_assurance_level: string;
|
|
30
|
+
authentication_methods: {
|
|
31
|
+
method: string;
|
|
32
|
+
completed_at: string;
|
|
33
|
+
}[];
|
|
34
|
+
identity: {
|
|
35
|
+
id: string;
|
|
36
|
+
traits: {
|
|
37
|
+
email: string;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
/** Inactive session (same shape, active: false). */
|
|
43
|
+
export declare const MOCK_INACTIVE_SESSION_RESPONSE: {
|
|
44
|
+
data: {
|
|
45
|
+
active: boolean;
|
|
46
|
+
id: string;
|
|
47
|
+
authenticated_at: string;
|
|
48
|
+
expires_at: string;
|
|
49
|
+
authenticator_assurance_level: string;
|
|
50
|
+
authentication_methods: {
|
|
51
|
+
method: string;
|
|
52
|
+
completed_at: string;
|
|
53
|
+
}[];
|
|
54
|
+
identity: {
|
|
55
|
+
id: string;
|
|
56
|
+
traits: {
|
|
57
|
+
email: string;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
/** Successful OAuth2 token introspection response. */
|
|
63
|
+
export declare const MOCK_OAUTH2_RESPONSE: {
|
|
64
|
+
data: {
|
|
65
|
+
active: boolean;
|
|
66
|
+
client_id: string;
|
|
67
|
+
sub: string;
|
|
68
|
+
scope: string;
|
|
69
|
+
aud: string[];
|
|
70
|
+
exp: number;
|
|
71
|
+
iat: number;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
/** Inactive OAuth2 token response. */
|
|
75
|
+
export declare const MOCK_INACTIVE_OAUTH2_RESPONSE: {
|
|
76
|
+
data: {
|
|
77
|
+
active: boolean;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
/** Permission check allowed response. */
|
|
81
|
+
export declare const PERMISSION_ALLOWED: {
|
|
82
|
+
data: {
|
|
83
|
+
allowed: boolean;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
/** Permission check denied response. */
|
|
87
|
+
export declare const PERMISSION_DENIED: {
|
|
88
|
+
data: {
|
|
89
|
+
allowed: boolean;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
/** Batch permission check: both results allowed. */
|
|
93
|
+
export declare const BATCH_BOTH_ALLOWED: {
|
|
94
|
+
data: {
|
|
95
|
+
results: {
|
|
96
|
+
allowed: boolean;
|
|
97
|
+
}[];
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
/** Batch permission check: server allowed, tool denied. */
|
|
101
|
+
export declare const BATCH_SERVER_ALLOWED_TOOL_DENIED: {
|
|
102
|
+
data: {
|
|
103
|
+
results: {
|
|
104
|
+
allowed: boolean;
|
|
105
|
+
}[];
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
/** Batch permission check: server denied. */
|
|
109
|
+
export declare const BATCH_SERVER_DENIED: {
|
|
110
|
+
data: {
|
|
111
|
+
results: {
|
|
112
|
+
allowed: boolean;
|
|
113
|
+
}[];
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
/** Create an Axios-shaped error with a response. */
|
|
117
|
+
export declare function makeAxiosError(status: number, body?: unknown, code?: string): Record<string, unknown>;
|
|
118
|
+
/** Create a Node.js network error (ECONNREFUSED, ETIMEDOUT, etc.). */
|
|
119
|
+
export declare function makeNetworkError(code?: string): NodeJS.ErrnoException;
|
|
120
|
+
/** Axios 429 rate-limit error. */
|
|
121
|
+
export declare function makeRateLimitError(): Record<string, unknown>;
|
|
122
|
+
/** Axios 403 with session_aal2_required error id. */
|
|
123
|
+
export declare function makeMfaRequiredError(): Record<string, unknown>;
|
|
124
|
+
/** Axios 401 with session_inactive error id. */
|
|
125
|
+
export declare function makeSessionInactiveError(): Record<string, unknown>;
|
|
126
|
+
/**
|
|
127
|
+
* Get all recorded trace spans from a client, optionally filtered by event.
|
|
128
|
+
*/
|
|
129
|
+
export declare function getTraceSpans(client: OryAgentClient, event?: TraceEvent): TraceSpan[];
|
|
130
|
+
/**
|
|
131
|
+
* Assert the tracer recorded the expected tool names for a given event type.
|
|
132
|
+
* Checks the `toolName` attribute on each span.
|
|
133
|
+
*/
|
|
134
|
+
export declare function expectTracedTools(client: OryAgentClient, event: TraceEvent, expectedTools: string[]): void;
|
|
135
|
+
/**
|
|
136
|
+
* Set standard Ory env vars for a configured + authenticated test.
|
|
137
|
+
* Returns a cleanup function that restores the previous state.
|
|
138
|
+
*/
|
|
139
|
+
export declare function setOryEnv(overrides?: Partial<{
|
|
140
|
+
projectUrl: string;
|
|
141
|
+
sessionToken: string;
|
|
142
|
+
oauth2Token: string;
|
|
143
|
+
subjectId: string;
|
|
144
|
+
namespace: string;
|
|
145
|
+
/**
|
|
146
|
+
* Permission mode for the test. Defaults to `"enforce"` so existing
|
|
147
|
+
* test assertions that exercise the deny path still see a block.
|
|
148
|
+
* Pass `"observe"` explicitly to exercise the new observe branch.
|
|
149
|
+
*/
|
|
150
|
+
permissionMode: "observe" | "enforce";
|
|
151
|
+
}>): () => void;
|
|
152
|
+
/**
|
|
153
|
+
* Point `XDG_CONFIG_HOME` at a fresh temp directory so `resolveConfig()`
|
|
154
|
+
* reads from a known-empty state instead of the developer's real
|
|
155
|
+
* `~/.config/ory-agent-plugins/config.json`. Returns a cleanup function
|
|
156
|
+
* that restores the env var and removes the temp directory.
|
|
157
|
+
*
|
|
158
|
+
* Use `saveConfig(...)` from `./config.js` inside the test to shape the
|
|
159
|
+
* isolated config (e.g. `saveConfig({ auditOnly: true })`).
|
|
160
|
+
*/
|
|
161
|
+
export declare function useTempConfigDir(): () => void;
|
|
162
|
+
/**
|
|
163
|
+
* Clear all Ory env vars (simulate unconfigured state).
|
|
164
|
+
*/
|
|
165
|
+
export declare function clearOryEnv(): void;
|
|
166
|
+
/** Stub verifySession to succeed. */
|
|
167
|
+
export declare function stubSessionSuccess(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
168
|
+
/** Stub verifySession to return inactive. */
|
|
169
|
+
export declare function stubSessionInactive(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
170
|
+
/** Stub verifySession to throw a network error. */
|
|
171
|
+
export declare function stubSessionNetworkError(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
172
|
+
/** Stub verifySession to throw MFA required. */
|
|
173
|
+
export declare function stubSessionMfaRequired(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
174
|
+
/** Stub verifySession to throw session_inactive. */
|
|
175
|
+
export declare function stubSessionExpired(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
176
|
+
/** Stub introspectToken to succeed. */
|
|
177
|
+
export declare function stubOAuth2Success(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
178
|
+
/** Stub introspectToken to return inactive. */
|
|
179
|
+
export declare function stubOAuth2Inactive(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
180
|
+
/** Stub checkPermission to allow. */
|
|
181
|
+
export declare function stubPermissionAllowed(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
182
|
+
/** Stub checkPermission to deny. */
|
|
183
|
+
export declare function stubPermissionDenied(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
184
|
+
/** Stub checkPermission to throw a network error. */
|
|
185
|
+
export declare function stubPermissionNetworkError(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
186
|
+
/** Stub checkPermission to throw a rate-limit error. */
|
|
187
|
+
export declare function stubPermissionRateLimited(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
188
|
+
/**
|
|
189
|
+
* Spy on the public `createRelationship` method and resolve successfully.
|
|
190
|
+
* Prefer this over stubbing the internal relationship API: `setAgentPrincipal`
|
|
191
|
+
* rebuilds the API instances when the agent token changes (e.g. inside
|
|
192
|
+
* `sessionStart`), which silently discards API-level stubs.
|
|
193
|
+
*/
|
|
194
|
+
export declare function spyRelationshipCreated(client: OryAgentClient): import("vitest").Mock<(check: import("./types.js").PermissionCheck, options?: {
|
|
195
|
+
spanAttributes?: Record<string, unknown>;
|
|
196
|
+
}) => Promise<{
|
|
197
|
+
created: boolean;
|
|
198
|
+
alreadyExisted: boolean;
|
|
199
|
+
}>>;
|
|
200
|
+
/** Spy on the public `createRelationship` method and reject. */
|
|
201
|
+
export declare function spyRelationshipCreateError(client: OryAgentClient): import("vitest").Mock<(check: import("./types.js").PermissionCheck, options?: {
|
|
202
|
+
spanAttributes?: Record<string, unknown>;
|
|
203
|
+
}) => Promise<{
|
|
204
|
+
created: boolean;
|
|
205
|
+
alreadyExisted: boolean;
|
|
206
|
+
}>>;
|
|
207
|
+
/** Stub both checkPermission (server allow) and batchCheckPermission (both allow). */
|
|
208
|
+
export declare function stubMcpAllowed(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
209
|
+
/** Stub checkPermission to deny (server-only MCP check). */
|
|
210
|
+
export declare function stubMcpServerDenied(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
211
|
+
/** Stub batchCheckPermission: server allowed, tool denied. */
|
|
212
|
+
export declare function stubMcpToolDenied(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
213
|
+
/** Stub checkPermission to throw network error (MCP fail-open). */
|
|
214
|
+
export declare function stubMcpNetworkError(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared test utilities for Ory agent plugin integration tests.
|
|
4
|
+
*
|
|
5
|
+
* These helpers provide mock Ory API responses, error factories, and
|
|
6
|
+
* common assertion patterns so that harness plugin tests focus on
|
|
7
|
+
* harness-specific behavior rather than duplicating boilerplate.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.BATCH_SERVER_DENIED = exports.BATCH_SERVER_ALLOWED_TOOL_DENIED = exports.BATCH_BOTH_ALLOWED = exports.PERMISSION_DENIED = exports.PERMISSION_ALLOWED = exports.MOCK_INACTIVE_OAUTH2_RESPONSE = exports.MOCK_OAUTH2_RESPONSE = exports.MOCK_INACTIVE_SESSION_RESPONSE = exports.MOCK_SESSION_RESPONSE = exports.runHarnessContractSuite = void 0;
|
|
44
|
+
exports.createMockClient = createMockClient;
|
|
45
|
+
exports.stubApi = stubApi;
|
|
46
|
+
exports.makeAxiosError = makeAxiosError;
|
|
47
|
+
exports.makeNetworkError = makeNetworkError;
|
|
48
|
+
exports.makeRateLimitError = makeRateLimitError;
|
|
49
|
+
exports.makeMfaRequiredError = makeMfaRequiredError;
|
|
50
|
+
exports.makeSessionInactiveError = makeSessionInactiveError;
|
|
51
|
+
exports.getTraceSpans = getTraceSpans;
|
|
52
|
+
exports.expectTracedTools = expectTracedTools;
|
|
53
|
+
exports.setOryEnv = setOryEnv;
|
|
54
|
+
exports.useTempConfigDir = useTempConfigDir;
|
|
55
|
+
exports.clearOryEnv = clearOryEnv;
|
|
56
|
+
exports.stubSessionSuccess = stubSessionSuccess;
|
|
57
|
+
exports.stubSessionInactive = stubSessionInactive;
|
|
58
|
+
exports.stubSessionNetworkError = stubSessionNetworkError;
|
|
59
|
+
exports.stubSessionMfaRequired = stubSessionMfaRequired;
|
|
60
|
+
exports.stubSessionExpired = stubSessionExpired;
|
|
61
|
+
exports.stubOAuth2Success = stubOAuth2Success;
|
|
62
|
+
exports.stubOAuth2Inactive = stubOAuth2Inactive;
|
|
63
|
+
exports.stubPermissionAllowed = stubPermissionAllowed;
|
|
64
|
+
exports.stubPermissionDenied = stubPermissionDenied;
|
|
65
|
+
exports.stubPermissionNetworkError = stubPermissionNetworkError;
|
|
66
|
+
exports.stubPermissionRateLimited = stubPermissionRateLimited;
|
|
67
|
+
exports.spyRelationshipCreated = spyRelationshipCreated;
|
|
68
|
+
exports.spyRelationshipCreateError = spyRelationshipCreateError;
|
|
69
|
+
exports.stubMcpAllowed = stubMcpAllowed;
|
|
70
|
+
exports.stubMcpServerDenied = stubMcpServerDenied;
|
|
71
|
+
exports.stubMcpToolDenied = stubMcpToolDenied;
|
|
72
|
+
exports.stubMcpNetworkError = stubMcpNetworkError;
|
|
73
|
+
const fs = __importStar(require("node:fs"));
|
|
74
|
+
const os = __importStar(require("node:os"));
|
|
75
|
+
const path = __importStar(require("node:path"));
|
|
76
|
+
const vitest_1 = require("vitest");
|
|
77
|
+
const client_js_1 = require("./client.js");
|
|
78
|
+
// Shared per-harness contract scenarios (session fail-open, gate
|
|
79
|
+
// translation, audit-only). See contract-suite.ts for the adapter shape.
|
|
80
|
+
var contract_suite_js_1 = require("./contract-suite.js");
|
|
81
|
+
Object.defineProperty(exports, "runHarnessContractSuite", { enumerable: true, get: function () { return contract_suite_js_1.runHarnessContractSuite; } });
|
|
82
|
+
// ─── Client Helpers ────────────────────────────────────────────────
|
|
83
|
+
/**
|
|
84
|
+
* Create an OryAgentClient with session caching disabled.
|
|
85
|
+
* Pass overrides to customize (e.g. a different harness name).
|
|
86
|
+
*/
|
|
87
|
+
function createMockClient(overrides) {
|
|
88
|
+
return new client_js_1.OryAgentClient({
|
|
89
|
+
projectUrl: "https://test.projects.oryapis.com",
|
|
90
|
+
apiKey: "test-api-key",
|
|
91
|
+
harness: "test",
|
|
92
|
+
sessionCacheTtlMs: 0,
|
|
93
|
+
...overrides,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Stub an internal API instance method on the client.
|
|
98
|
+
* Returns a vi.fn mock so callers can assert on calls.
|
|
99
|
+
*/
|
|
100
|
+
function stubApi(client, api, method, impl) {
|
|
101
|
+
const instance = client[api];
|
|
102
|
+
instance[method] = vitest_1.vi.fn(impl);
|
|
103
|
+
return instance[method];
|
|
104
|
+
}
|
|
105
|
+
// ─── Mock API Responses ────────────────────────────────────────────
|
|
106
|
+
/** Successful session verification response (wraps Ory API shape). */
|
|
107
|
+
exports.MOCK_SESSION_RESPONSE = {
|
|
108
|
+
data: {
|
|
109
|
+
id: "session-abc",
|
|
110
|
+
active: true,
|
|
111
|
+
authenticated_at: "2026-04-17T10:00:00Z",
|
|
112
|
+
expires_at: "2026-04-18T10:00:00Z",
|
|
113
|
+
authenticator_assurance_level: "aal1",
|
|
114
|
+
authentication_methods: [
|
|
115
|
+
{ method: "password", completed_at: "2026-04-17T10:00:00Z" },
|
|
116
|
+
],
|
|
117
|
+
identity: {
|
|
118
|
+
id: "identity-123",
|
|
119
|
+
traits: { email: "dev@example.com" },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
/** Inactive session (same shape, active: false). */
|
|
124
|
+
exports.MOCK_INACTIVE_SESSION_RESPONSE = {
|
|
125
|
+
data: { ...exports.MOCK_SESSION_RESPONSE.data, active: false },
|
|
126
|
+
};
|
|
127
|
+
/** Successful OAuth2 token introspection response. */
|
|
128
|
+
exports.MOCK_OAUTH2_RESPONSE = {
|
|
129
|
+
data: {
|
|
130
|
+
active: true,
|
|
131
|
+
client_id: "agent-client",
|
|
132
|
+
sub: "user:alice",
|
|
133
|
+
scope: "tools:invoke",
|
|
134
|
+
aud: ["api"],
|
|
135
|
+
exp: 1700000000,
|
|
136
|
+
iat: 1699999000,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
/** Inactive OAuth2 token response. */
|
|
140
|
+
exports.MOCK_INACTIVE_OAUTH2_RESPONSE = {
|
|
141
|
+
data: { active: false },
|
|
142
|
+
};
|
|
143
|
+
/** Permission check allowed response. */
|
|
144
|
+
exports.PERMISSION_ALLOWED = { data: { allowed: true } };
|
|
145
|
+
/** Permission check denied response. */
|
|
146
|
+
exports.PERMISSION_DENIED = { data: { allowed: false } };
|
|
147
|
+
/** Batch permission check: both results allowed. */
|
|
148
|
+
exports.BATCH_BOTH_ALLOWED = {
|
|
149
|
+
data: { results: [{ allowed: true }, { allowed: true }] },
|
|
150
|
+
};
|
|
151
|
+
/** Batch permission check: server allowed, tool denied. */
|
|
152
|
+
exports.BATCH_SERVER_ALLOWED_TOOL_DENIED = {
|
|
153
|
+
data: { results: [{ allowed: true }, { allowed: false }] },
|
|
154
|
+
};
|
|
155
|
+
/** Batch permission check: server denied. */
|
|
156
|
+
exports.BATCH_SERVER_DENIED = {
|
|
157
|
+
data: { results: [{ allowed: false }, { allowed: true }] },
|
|
158
|
+
};
|
|
159
|
+
// ─── Error Factories ───────────────────────────────────────────────
|
|
160
|
+
/** Create an Axios-shaped error with a response. */
|
|
161
|
+
function makeAxiosError(status, body, code) {
|
|
162
|
+
const err = {
|
|
163
|
+
isAxiosError: true,
|
|
164
|
+
message: `Request failed with status code ${status}`,
|
|
165
|
+
response: { status, data: body ?? {} },
|
|
166
|
+
};
|
|
167
|
+
if (code)
|
|
168
|
+
err.code = code;
|
|
169
|
+
return err;
|
|
170
|
+
}
|
|
171
|
+
/** Create a Node.js network error (ECONNREFUSED, ETIMEDOUT, etc.). */
|
|
172
|
+
function makeNetworkError(code = "ECONNREFUSED") {
|
|
173
|
+
const err = new Error(`connect ${code}`);
|
|
174
|
+
err.code = code;
|
|
175
|
+
return err;
|
|
176
|
+
}
|
|
177
|
+
/** Axios 429 rate-limit error. */
|
|
178
|
+
function makeRateLimitError() {
|
|
179
|
+
return makeAxiosError(429);
|
|
180
|
+
}
|
|
181
|
+
/** Axios 403 with session_aal2_required error id. */
|
|
182
|
+
function makeMfaRequiredError() {
|
|
183
|
+
return makeAxiosError(403, {
|
|
184
|
+
error: { id: "session_aal2_required", message: "MFA required" },
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/** Axios 401 with session_inactive error id. */
|
|
188
|
+
function makeSessionInactiveError() {
|
|
189
|
+
return makeAxiosError(401, {
|
|
190
|
+
error: { id: "session_inactive", message: "No active session" },
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// ─── Trace Span Assertions ────────────────────────────────────────
|
|
194
|
+
/**
|
|
195
|
+
* Get all recorded trace spans from a client, optionally filtered by event.
|
|
196
|
+
*/
|
|
197
|
+
function getTraceSpans(client, event) {
|
|
198
|
+
const all = client.tracer.spans();
|
|
199
|
+
return event ? all.filter((s) => s.event === event) : all;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Assert the tracer recorded the expected tool names for a given event type.
|
|
203
|
+
* Checks the `toolName` attribute on each span.
|
|
204
|
+
*/
|
|
205
|
+
function expectTracedTools(client, event, expectedTools) {
|
|
206
|
+
const spans = getTraceSpans(client, event);
|
|
207
|
+
const tools = spans.map((s) => s.attributes?.toolName);
|
|
208
|
+
for (const tool of expectedTools) {
|
|
209
|
+
if (!tools.includes(tool)) {
|
|
210
|
+
throw new Error(`Expected trace event "${event}" for tool "${tool}" but got: [${tools.join(", ")}]`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (spans.length !== expectedTools.length) {
|
|
214
|
+
throw new Error(`Expected ${expectedTools.length} "${event}" spans but got ${spans.length}: [${tools.join(", ")}]`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// ─── Environment Helpers ───────────────────────────────────────────
|
|
218
|
+
/**
|
|
219
|
+
* Set standard Ory env vars for a configured + authenticated test.
|
|
220
|
+
* Returns a cleanup function that restores the previous state.
|
|
221
|
+
*/
|
|
222
|
+
function setOryEnv(overrides = {}) {
|
|
223
|
+
const saved = {};
|
|
224
|
+
const vars = {
|
|
225
|
+
ORY_PROJECT_URL: "projectUrl" in overrides
|
|
226
|
+
? overrides.projectUrl
|
|
227
|
+
: "https://test.projects.oryapis.com",
|
|
228
|
+
ORY_SESSION_TOKEN: "sessionToken" in overrides ? overrides.sessionToken : "valid-token",
|
|
229
|
+
ORY_OAUTH2_TOKEN: overrides.oauth2Token,
|
|
230
|
+
ORY_AGENT_SUBJECT_ID: overrides.subjectId,
|
|
231
|
+
ORY_PERMISSION_NAMESPACE: overrides.namespace,
|
|
232
|
+
ORY_PERMISSION_MODE: "permissionMode" in overrides ? overrides.permissionMode : "enforce",
|
|
233
|
+
};
|
|
234
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
235
|
+
saved[key] = process.env[key];
|
|
236
|
+
if (value !== undefined) {
|
|
237
|
+
process.env[key] = value;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
delete process.env[key];
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return () => {
|
|
244
|
+
for (const [key, value] of Object.entries(saved)) {
|
|
245
|
+
if (value !== undefined) {
|
|
246
|
+
process.env[key] = value;
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
delete process.env[key];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Point `XDG_CONFIG_HOME` at a fresh temp directory so `resolveConfig()`
|
|
256
|
+
* reads from a known-empty state instead of the developer's real
|
|
257
|
+
* `~/.config/ory-agent-plugins/config.json`. Returns a cleanup function
|
|
258
|
+
* that restores the env var and removes the temp directory.
|
|
259
|
+
*
|
|
260
|
+
* Use `saveConfig(...)` from `./config.js` inside the test to shape the
|
|
261
|
+
* isolated config (e.g. `saveConfig({ auditOnly: true })`).
|
|
262
|
+
*/
|
|
263
|
+
function useTempConfigDir() {
|
|
264
|
+
const saved = process.env.XDG_CONFIG_HOME;
|
|
265
|
+
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "ory-test-config-"));
|
|
266
|
+
process.env.XDG_CONFIG_HOME = tmpHome;
|
|
267
|
+
return () => {
|
|
268
|
+
if (saved !== undefined)
|
|
269
|
+
process.env.XDG_CONFIG_HOME = saved;
|
|
270
|
+
else
|
|
271
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
272
|
+
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Clear all Ory env vars (simulate unconfigured state).
|
|
277
|
+
*/
|
|
278
|
+
function clearOryEnv() {
|
|
279
|
+
delete process.env.ORY_PROJECT_URL;
|
|
280
|
+
delete process.env.ORY_API_KEY;
|
|
281
|
+
delete process.env.ORY_SESSION_TOKEN;
|
|
282
|
+
delete process.env.ORY_OAUTH2_TOKEN;
|
|
283
|
+
delete process.env.ORY_AGENT_SUBJECT_ID;
|
|
284
|
+
delete process.env.ORY_USER_SUBJECT_ID;
|
|
285
|
+
delete process.env.ORY_USER_SUBJECT_NAMESPACE;
|
|
286
|
+
delete process.env.ORY_PERMISSION_NAMESPACE;
|
|
287
|
+
// Intentionally leave ORY_PERMISSION_MODE alone — it's typically set
|
|
288
|
+
// once at the top of a test file to pin the historical enforce-mode
|
|
289
|
+
// behavior, and unconfigured-Ory tests don't exercise permission
|
|
290
|
+
// checks anyway.
|
|
291
|
+
}
|
|
292
|
+
// ─── Stub Presets ──────────────────────────────────────────────────
|
|
293
|
+
/** Stub verifySession to succeed. */
|
|
294
|
+
function stubSessionSuccess(client) {
|
|
295
|
+
return stubApi(client, "frontend", "toSession", () => Promise.resolve(exports.MOCK_SESSION_RESPONSE));
|
|
296
|
+
}
|
|
297
|
+
/** Stub verifySession to return inactive. */
|
|
298
|
+
function stubSessionInactive(client) {
|
|
299
|
+
return stubApi(client, "frontend", "toSession", () => Promise.resolve(exports.MOCK_INACTIVE_SESSION_RESPONSE));
|
|
300
|
+
}
|
|
301
|
+
/** Stub verifySession to throw a network error. */
|
|
302
|
+
function stubSessionNetworkError(client) {
|
|
303
|
+
return stubApi(client, "frontend", "toSession", () => Promise.reject(makeNetworkError()));
|
|
304
|
+
}
|
|
305
|
+
/** Stub verifySession to throw MFA required. */
|
|
306
|
+
function stubSessionMfaRequired(client) {
|
|
307
|
+
return stubApi(client, "frontend", "toSession", () => Promise.reject(makeMfaRequiredError()));
|
|
308
|
+
}
|
|
309
|
+
/** Stub verifySession to throw session_inactive. */
|
|
310
|
+
function stubSessionExpired(client) {
|
|
311
|
+
return stubApi(client, "frontend", "toSession", () => Promise.reject(makeSessionInactiveError()));
|
|
312
|
+
}
|
|
313
|
+
/** Stub introspectToken to succeed. */
|
|
314
|
+
function stubOAuth2Success(client) {
|
|
315
|
+
return stubApi(client, "oauth2", "introspectOAuth2Token", () => Promise.resolve(exports.MOCK_OAUTH2_RESPONSE));
|
|
316
|
+
}
|
|
317
|
+
/** Stub introspectToken to return inactive. */
|
|
318
|
+
function stubOAuth2Inactive(client) {
|
|
319
|
+
return stubApi(client, "oauth2", "introspectOAuth2Token", () => Promise.resolve(exports.MOCK_INACTIVE_OAUTH2_RESPONSE));
|
|
320
|
+
}
|
|
321
|
+
/** Stub checkPermission to allow. */
|
|
322
|
+
function stubPermissionAllowed(client) {
|
|
323
|
+
return stubApi(client, "permission", "checkPermission", () => Promise.resolve(exports.PERMISSION_ALLOWED));
|
|
324
|
+
}
|
|
325
|
+
/** Stub checkPermission to deny. */
|
|
326
|
+
function stubPermissionDenied(client) {
|
|
327
|
+
return stubApi(client, "permission", "checkPermission", () => Promise.resolve(exports.PERMISSION_DENIED));
|
|
328
|
+
}
|
|
329
|
+
/** Stub checkPermission to throw a network error. */
|
|
330
|
+
function stubPermissionNetworkError(client) {
|
|
331
|
+
return stubApi(client, "permission", "checkPermission", () => Promise.reject(makeNetworkError()));
|
|
332
|
+
}
|
|
333
|
+
/** Stub checkPermission to throw a rate-limit error. */
|
|
334
|
+
function stubPermissionRateLimited(client) {
|
|
335
|
+
return stubApi(client, "permission", "checkPermission", () => Promise.reject(makeRateLimitError()));
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Spy on the public `createRelationship` method and resolve successfully.
|
|
339
|
+
* Prefer this over stubbing the internal relationship API: `setAgentPrincipal`
|
|
340
|
+
* rebuilds the API instances when the agent token changes (e.g. inside
|
|
341
|
+
* `sessionStart`), which silently discards API-level stubs.
|
|
342
|
+
*/
|
|
343
|
+
function spyRelationshipCreated(client) {
|
|
344
|
+
return vitest_1.vi
|
|
345
|
+
.spyOn(client, "createRelationship")
|
|
346
|
+
.mockResolvedValue({ created: true, alreadyExisted: false });
|
|
347
|
+
}
|
|
348
|
+
/** Spy on the public `createRelationship` method and reject. */
|
|
349
|
+
function spyRelationshipCreateError(client) {
|
|
350
|
+
return vitest_1.vi
|
|
351
|
+
.spyOn(client, "createRelationship")
|
|
352
|
+
.mockRejectedValue(new Error("relationship write failed"));
|
|
353
|
+
}
|
|
354
|
+
// ─── MCP Stub Presets ─────────────────────────────────────────────
|
|
355
|
+
/** Stub both checkPermission (server allow) and batchCheckPermission (both allow). */
|
|
356
|
+
function stubMcpAllowed(client) {
|
|
357
|
+
stubApi(client, "permission", "checkPermission", () => Promise.resolve(exports.PERMISSION_ALLOWED));
|
|
358
|
+
return stubApi(client, "permission", "batchCheckPermission", () => Promise.resolve(exports.BATCH_BOTH_ALLOWED));
|
|
359
|
+
}
|
|
360
|
+
/** Stub checkPermission to deny (server-only MCP check). */
|
|
361
|
+
function stubMcpServerDenied(client) {
|
|
362
|
+
return stubApi(client, "permission", "checkPermission", () => Promise.resolve(exports.PERMISSION_DENIED));
|
|
363
|
+
}
|
|
364
|
+
/** Stub batchCheckPermission: server allowed, tool denied. */
|
|
365
|
+
function stubMcpToolDenied(client) {
|
|
366
|
+
stubApi(client, "permission", "checkPermission", () => Promise.resolve(exports.PERMISSION_ALLOWED));
|
|
367
|
+
return stubApi(client, "permission", "batchCheckPermission", () => Promise.resolve(exports.BATCH_SERVER_ALLOWED_TOOL_DENIED));
|
|
368
|
+
}
|
|
369
|
+
/** Stub checkPermission to throw network error (MCP fail-open). */
|
|
370
|
+
function stubMcpNetworkError(client) {
|
|
371
|
+
return stubApi(client, "permission", "checkPermission", () => Promise.reject(makeNetworkError()));
|
|
372
|
+
}
|
package/dist/tool-catalog.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ export declare const HARNESS_TOOL_CATALOG: {
|
|
|
20
20
|
readonly "gemini-cli": readonly ["read_file", "write_file", "edit_file", "list_files", "search_files", "shell", "web_search"];
|
|
21
21
|
readonly openclaw: readonly ["execute_command", "read_file", "write_file", "list_directory", "search_files", "browser"];
|
|
22
22
|
readonly opencode: readonly ["read", "write", "edit", "bash", "glob", "grep", "webfetch"];
|
|
23
|
+
readonly continue: readonly ["Bash", "Read", "Edit", "Write", "Grep", "Glob", "WebSearch"];
|
|
24
|
+
readonly goose: readonly ["shell", "text_editor", "read_file", "write_file", "list_windows", "screen_capture"];
|
|
25
|
+
readonly cline: readonly ["execute_command", "read_file", "write_to_file", "replace_in_file", "search_files", "list_files", "use_mcp_tool"];
|
|
26
|
+
readonly amp: readonly ["Bash", "Read", "create_file", "edit_file", "undo_edit", "glob", "Grep", "finder", "read_web_page", "web_search", "todo_read", "todo_write", "oracle", "Task"];
|
|
27
|
+
readonly pi: readonly ["read", "write", "edit", "bash", "grep", "find", "ls"];
|
|
28
|
+
readonly antigravity: readonly ["run_command", "view_file", "write_to_file", "replace_file_content", "multi_replace_file_content", "list_directory", "grep_search", "find", "read_url_content", "search_web", "call_mcp_tool"];
|
|
23
29
|
};
|
|
24
30
|
/** Names of the harnesses with a known tool catalog. */
|
|
25
31
|
export type KnownHarness = keyof typeof HARNESS_TOOL_CATALOG;
|
package/dist/tool-catalog.js
CHANGED
|
@@ -53,6 +53,54 @@ exports.HARNESS_TOOL_CATALOG = {
|
|
|
53
53
|
"browser",
|
|
54
54
|
],
|
|
55
55
|
opencode: ["read", "write", "edit", "bash", "glob", "grep", "webfetch"],
|
|
56
|
+
continue: ["Bash", "Read", "Edit", "Write", "Grep", "Glob", "WebSearch"],
|
|
57
|
+
goose: [
|
|
58
|
+
"shell",
|
|
59
|
+
"text_editor",
|
|
60
|
+
"read_file",
|
|
61
|
+
"write_file",
|
|
62
|
+
"list_windows",
|
|
63
|
+
"screen_capture",
|
|
64
|
+
],
|
|
65
|
+
cline: [
|
|
66
|
+
"execute_command",
|
|
67
|
+
"read_file",
|
|
68
|
+
"write_to_file",
|
|
69
|
+
"replace_in_file",
|
|
70
|
+
"search_files",
|
|
71
|
+
"list_files",
|
|
72
|
+
"use_mcp_tool",
|
|
73
|
+
],
|
|
74
|
+
amp: [
|
|
75
|
+
"Bash",
|
|
76
|
+
"Read",
|
|
77
|
+
"create_file",
|
|
78
|
+
"edit_file",
|
|
79
|
+
"undo_edit",
|
|
80
|
+
"glob",
|
|
81
|
+
"Grep",
|
|
82
|
+
"finder",
|
|
83
|
+
"read_web_page",
|
|
84
|
+
"web_search",
|
|
85
|
+
"todo_read",
|
|
86
|
+
"todo_write",
|
|
87
|
+
"oracle",
|
|
88
|
+
"Task",
|
|
89
|
+
],
|
|
90
|
+
pi: ["read", "write", "edit", "bash", "grep", "find", "ls"],
|
|
91
|
+
antigravity: [
|
|
92
|
+
"run_command",
|
|
93
|
+
"view_file",
|
|
94
|
+
"write_to_file",
|
|
95
|
+
"replace_file_content",
|
|
96
|
+
"multi_replace_file_content",
|
|
97
|
+
"list_directory",
|
|
98
|
+
"grep_search",
|
|
99
|
+
"find",
|
|
100
|
+
"read_url_content",
|
|
101
|
+
"search_web",
|
|
102
|
+
"call_mcp_tool",
|
|
103
|
+
],
|
|
56
104
|
};
|
|
57
105
|
exports.KNOWN_HARNESSES = Object.keys(exports.HARNESS_TOOL_CATALOG);
|
|
58
106
|
/**
|
|
@@ -92,6 +140,12 @@ exports.INTERACTIVE_TOOL_CATALOG = {
|
|
|
92
140
|
"gemini-cli": [],
|
|
93
141
|
openclaw: [],
|
|
94
142
|
opencode: [],
|
|
143
|
+
continue: [],
|
|
144
|
+
goose: [],
|
|
145
|
+
cline: [],
|
|
146
|
+
amp: [],
|
|
147
|
+
pi: [],
|
|
148
|
+
antigravity: [],
|
|
95
149
|
};
|
|
96
150
|
function parseEnvInteractiveTools() {
|
|
97
151
|
const raw = process.env.ORY_INTERACTIVE_TOOLS;
|