@ory/argus 0.13.9 → 1.0.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.
Files changed (133) hide show
  1. package/README.md +31 -45
  2. package/assets/commands/temporal-up.md +1 -1
  3. package/assets/skills/auth-setup/SKILL.md +1 -1
  4. package/assets/skills/local-dev/SKILL.md +17 -7
  5. package/assets/skills/ory-build-agent/SKILL.md +43 -97
  6. package/assets/skills/ory-e2b-sandbox/SKILL.md +18 -18
  7. package/assets/skills/ory-temporal-worker/SKILL.md +38 -42
  8. package/assets/skills/permissions-onboarding/SKILL.md +131 -104
  9. package/dist/adapters.d.ts +93 -30
  10. package/dist/adapters.js +464 -136
  11. package/dist/agent-auth.d.ts +258 -68
  12. package/dist/agent-auth.js +998 -202
  13. package/dist/auth-store.d.ts +37 -2
  14. package/dist/auth-store.js +37 -3
  15. package/dist/auth.d.ts +40 -4
  16. package/dist/auth.js +247 -19
  17. package/dist/bash-parser.d.ts +98 -0
  18. package/dist/bash-parser.js +396 -0
  19. package/dist/branding.d.ts +128 -0
  20. package/dist/branding.js +151 -0
  21. package/dist/build-info.json +4 -4
  22. package/dist/cli-invocation.d.ts +1 -1
  23. package/dist/cli-invocation.js +2 -1
  24. package/dist/cli.d.ts +20 -29
  25. package/dist/cli.js +271 -278
  26. package/dist/client.d.ts +175 -138
  27. package/dist/client.js +672 -391
  28. package/dist/config.d.ts +249 -57
  29. package/dist/config.js +486 -62
  30. package/dist/context.d.ts +10 -0
  31. package/dist/context.js +21 -0
  32. package/dist/contract-suite.d.ts +8 -8
  33. package/dist/contract-suite.js +88 -69
  34. package/dist/denial.d.ts +36 -3
  35. package/dist/denial.js +79 -10
  36. package/dist/event-reporter.d.ts +77 -0
  37. package/dist/event-reporter.js +776 -0
  38. package/dist/external-registrations-main.d.ts +10 -0
  39. package/dist/external-registrations-main.js +38 -0
  40. package/dist/external-registrations.d.ts +79 -0
  41. package/dist/external-registrations.js +188 -0
  42. package/dist/help-cli.d.ts +39 -0
  43. package/dist/help-cli.js +55 -0
  44. package/dist/hook-timeout.d.ts +64 -0
  45. package/dist/hook-timeout.js +88 -0
  46. package/dist/index.d.ts +31 -19
  47. package/dist/index.js +182 -31
  48. package/dist/lifecycle.d.ts +3 -3
  49. package/dist/lifecycle.js +38 -6
  50. package/dist/local/cli.js +11 -6
  51. package/dist/local/configs.d.ts +74 -18
  52. package/dist/local/configs.js +291 -84
  53. package/dist/local/health.d.ts +14 -0
  54. package/dist/local/health.js +50 -4
  55. package/dist/local/index.d.ts +2 -2
  56. package/dist/local/index.js +24 -10
  57. package/dist/local/manager.d.ts +20 -1
  58. package/dist/local/manager.js +160 -39
  59. package/dist/local/ports.d.ts +158 -0
  60. package/dist/local/ports.js +443 -0
  61. package/dist/local/seed.d.ts +22 -25
  62. package/dist/local/seed.js +88 -56
  63. package/dist/logger.d.ts +54 -25
  64. package/dist/logger.js +329 -63
  65. package/dist/mcp.d.ts +2 -2
  66. package/dist/mcp.js +10 -5
  67. package/dist/mirror-bootstrap.d.ts +48 -0
  68. package/dist/mirror-bootstrap.js +254 -0
  69. package/dist/opl.d.ts +289 -0
  70. package/dist/opl.js +446 -0
  71. package/dist/permission-mode.d.ts +87 -0
  72. package/dist/permission-mode.js +307 -0
  73. package/dist/permissions-cli.d.ts +13 -49
  74. package/dist/permissions-cli.js +154 -348
  75. package/dist/permissions.d.ts +148 -38
  76. package/dist/permissions.js +591 -45
  77. package/dist/post-install.d.ts +33 -0
  78. package/dist/post-install.js +127 -0
  79. package/dist/read-credential.d.ts +65 -0
  80. package/dist/read-credential.js +86 -0
  81. package/dist/registry/cli.js +5 -2
  82. package/dist/registry/config.d.ts +0 -17
  83. package/dist/registry/config.js +0 -23
  84. package/dist/registry/index.d.ts +1 -1
  85. package/dist/registry/index.js +2 -2
  86. package/dist/registry/manager.d.ts +4 -21
  87. package/dist/registry/manager.js +83 -55
  88. package/dist/runtime-credential.d.ts +140 -0
  89. package/dist/runtime-credential.js +572 -0
  90. package/dist/runtime.d.ts +408 -0
  91. package/dist/runtime.js +748 -0
  92. package/dist/setup.d.ts +23 -28
  93. package/dist/setup.js +57 -84
  94. package/dist/status-cli.d.ts +29 -13
  95. package/dist/status-cli.js +124 -144
  96. package/dist/status-data.d.ts +195 -0
  97. package/dist/status-data.js +333 -0
  98. package/dist/status-system.d.ts +24 -0
  99. package/dist/status-system.js +56 -0
  100. package/dist/subject.d.ts +126 -20
  101. package/dist/subject.js +215 -30
  102. package/dist/testing.d.ts +74 -38
  103. package/dist/testing.js +185 -68
  104. package/dist/tool-catalog.d.ts +53 -11
  105. package/dist/tool-catalog.js +164 -13
  106. package/dist/tool-metadata.d.ts +7 -6
  107. package/dist/tool-metadata.js +6 -5
  108. package/dist/types.d.ts +11 -1
  109. package/dist/uninstall.d.ts +74 -19
  110. package/dist/uninstall.js +224 -49
  111. package/dist/user-login.d.ts +22 -16
  112. package/dist/user-login.js +67 -96
  113. package/dist/watch-cli.d.ts +6 -0
  114. package/dist/watch-cli.js +217 -0
  115. package/package.json +3 -11
  116. package/dist/dev.d.ts +0 -103
  117. package/dist/dev.js +0 -584
  118. package/dist/interactive-setup.d.ts +0 -165
  119. package/dist/interactive-setup.js +0 -1546
  120. package/dist/local/jaeger-main.d.ts +0 -13
  121. package/dist/local/jaeger-main.js +0 -85
  122. package/dist/local/jaeger.d.ts +0 -50
  123. package/dist/local/jaeger.js +0 -162
  124. package/dist/otel/exporter.d.ts +0 -17
  125. package/dist/otel/exporter.js +0 -12
  126. package/dist/otel/index.d.ts +0 -2
  127. package/dist/otel/index.js +0 -8
  128. package/dist/otel/otlp.d.ts +0 -103
  129. package/dist/otel/otlp.js +0 -385
  130. package/dist/tracer.d.ts +0 -190
  131. package/dist/tracer.js +0 -481
  132. package/dist/watch-sandbox.d.ts +0 -9
  133. package/dist/watch-sandbox.js +0 -81
package/dist/testing.js CHANGED
@@ -40,19 +40,21 @@ var __importStar = (this && this.__importStar) || (function () {
40
40
  };
41
41
  })();
42
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;
43
+ exports.TEST_OAUTH2_CLIENT_ID = exports.TEST_AGENT_SECURITY_URL = exports.TEST_PROJECT_URL = 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
44
  exports.createMockClient = createMockClient;
45
- exports.stubRelationshipExists = stubRelationshipExists;
45
+ exports.spyRecordDelegation = spyRecordDelegation;
46
46
  exports.stubApi = stubApi;
47
47
  exports.makeAxiosError = makeAxiosError;
48
48
  exports.makeNetworkError = makeNetworkError;
49
49
  exports.makeRateLimitError = makeRateLimitError;
50
50
  exports.makeMfaRequiredError = makeMfaRequiredError;
51
51
  exports.makeSessionInactiveError = makeSessionInactiveError;
52
- exports.getTraceSpans = getTraceSpans;
53
- exports.expectTracedTools = expectTracedTools;
52
+ exports.getActivityEvents = getActivityEvents;
53
+ exports.expectActivityTools = expectActivityTools;
54
54
  exports.setOryEnv = setOryEnv;
55
55
  exports.useTempConfigDir = useTempConfigDir;
56
+ exports.connectSecurity = connectSecurity;
57
+ exports.disconnectSecurity = disconnectSecurity;
56
58
  exports.clearOryEnv = clearOryEnv;
57
59
  exports.stubSessionSuccess = stubSessionSuccess;
58
60
  exports.stubSessionInactive = stubSessionInactive;
@@ -61,12 +63,11 @@ exports.stubSessionMfaRequired = stubSessionMfaRequired;
61
63
  exports.stubSessionExpired = stubSessionExpired;
62
64
  exports.stubOAuth2Success = stubOAuth2Success;
63
65
  exports.stubOAuth2Inactive = stubOAuth2Inactive;
66
+ exports.setTestPermissionMode = setTestPermissionMode;
64
67
  exports.stubPermissionAllowed = stubPermissionAllowed;
65
68
  exports.stubPermissionDenied = stubPermissionDenied;
66
69
  exports.stubPermissionNetworkError = stubPermissionNetworkError;
67
70
  exports.stubPermissionRateLimited = stubPermissionRateLimited;
68
- exports.spyRelationshipCreated = spyRelationshipCreated;
69
- exports.spyRelationshipCreateError = spyRelationshipCreateError;
70
71
  exports.stubMcpAllowed = stubMcpAllowed;
71
72
  exports.stubMcpServerDenied = stubMcpServerDenied;
72
73
  exports.stubMcpToolDenied = stubMcpToolDenied;
@@ -76,8 +77,12 @@ const os = __importStar(require("node:os"));
76
77
  const path = __importStar(require("node:path"));
77
78
  const vitest_1 = require("vitest");
78
79
  const client_js_1 = require("./client.js");
80
+ const config_js_1 = require("./config.js");
81
+ const opl_js_1 = require("./opl.js");
82
+ const permission_mode_js_1 = require("./permission-mode.js");
79
83
  // Shared per-harness contract scenarios (session fail-open, gate
80
- // translation, audit-only). See contract-suite.ts for the adapter shape.
84
+ // translation, Agent Security not connected). See contract-suite.ts for the
85
+ // adapter shape.
81
86
  var contract_suite_js_1 = require("./contract-suite.js");
82
87
  Object.defineProperty(exports, "runHarnessContractSuite", { enumerable: true, get: function () { return contract_suite_js_1.runHarnessContractSuite; } });
83
88
  // ─── Client Helpers ────────────────────────────────────────────────
@@ -88,29 +93,31 @@ Object.defineProperty(exports, "runHarnessContractSuite", { enumerable: true, ge
88
93
  function createMockClient(overrides) {
89
94
  const client = new client_js_1.OryAgentClient({
90
95
  projectUrl: "https://test.projects.oryapis.com",
91
- apiKey: "test-api-key",
92
96
  harness: "test",
93
97
  sessionCacheTtlMs: 0,
94
98
  ...overrides,
95
99
  });
96
- // `ensureRelationship` probes for the exact tuple before writing. Default
97
- // to "no tuple exists" so a plain createRelationship-asserting test sees the
98
- // write proceed; tests that model an already-present tuple override this via
99
- // `stubRelationshipExists`. We spy the public `relationshipExists` method
100
- // (not the internal getRelationships API) because setAgentPrincipal rebuilds
101
- // the Keto API instance and would discard an API-level stub.
102
- vitest_1.vi.spyOn(client, "relationshipExists").mockResolvedValue(false);
100
+ // Delegation edges are recorded through the agent-security broker (a real
101
+ // `fetch` in production). Default it to a resolved no-op so session-start /
102
+ // sub-agent paths under test never make a network call; tests that assert the
103
+ // broker call override this via `spyRecordDelegation`.
104
+ vitest_1.vi.spyOn(client, "recordDelegation").mockResolvedValue({
105
+ nodeId: "agent:mock-node",
106
+ delegationChain: [],
107
+ });
103
108
  return client;
104
109
  }
105
110
  /**
106
- * Control what the exact-tuple existence probe ({@link OryAgentClient.relationshipExists},
107
- * used by `ensureRelationship`) reports. Pass `true` to model a tuple that is
108
- * already stored (the write is then skipped), `false` for absent (the write
109
- * proceeds). Returns the spy so callers can e.g. `.mockRejectedValue(...)` to
110
- * model a failed probe.
111
+ * Spy on {@link OryAgentClient.recordDelegation} (the agent-security broker
112
+ * call the delegation writers use). Resolves to `result` (a stub node by
113
+ * default). Returns the spy so callers assert the broker inputs or
114
+ * `.mockRejectedValue(...)` a broker failure.
111
115
  */
112
- function stubRelationshipExists(client, exists) {
113
- return vitest_1.vi.spyOn(client, "relationshipExists").mockResolvedValue(exists);
116
+ function spyRecordDelegation(client, result = {
117
+ nodeId: "agent:mock-node",
118
+ delegationChain: [],
119
+ }) {
120
+ return vitest_1.vi.spyOn(client, "recordDelegation").mockResolvedValue(result);
114
121
  }
115
122
  /**
116
123
  * Stub an internal API instance method on the client.
@@ -209,28 +216,27 @@ function makeSessionInactiveError() {
209
216
  error: { id: "session_inactive", message: "No active session" },
210
217
  });
211
218
  }
212
- // ─── Trace Span Assertions ────────────────────────────────────────
219
+ // ─── Activity Assertions ──────────────────────────────────────────
213
220
  /**
214
- * Get all recorded trace spans from a client, optionally filtered by event.
221
+ * Get all recorded activity events from a client, optionally filtered by event.
215
222
  */
216
- function getTraceSpans(client, event) {
217
- const all = client.tracer.spans();
218
- return event ? all.filter((s) => s.event === event) : all;
223
+ function getActivityEvents(client, event) {
224
+ const all = client.logger.activities();
225
+ return event ? all.filter((entry) => entry.event === event) : all;
219
226
  }
220
227
  /**
221
- * Assert the tracer recorded the expected tool names for a given event type.
222
- * Checks the `toolName` attribute on each span.
228
+ * Assert the activity log recorded the expected tool names for an event type.
223
229
  */
224
- function expectTracedTools(client, event, expectedTools) {
225
- const spans = getTraceSpans(client, event);
226
- const tools = spans.map((s) => s.attributes?.toolName);
230
+ function expectActivityTools(client, event, expectedTools) {
231
+ const events = getActivityEvents(client, event);
232
+ const tools = events.map((entry) => entry.attributes?.toolName);
227
233
  for (const tool of expectedTools) {
228
234
  if (!tools.includes(tool)) {
229
- throw new Error(`Expected trace event "${event}" for tool "${tool}" but got: [${tools.join(", ")}]`);
235
+ throw new Error(`Expected activity event "${event}" for tool "${tool}" but got: [${tools.join(", ")}]`);
230
236
  }
231
237
  }
232
- if (spans.length !== expectedTools.length) {
233
- throw new Error(`Expected ${expectedTools.length} "${event}" spans but got ${spans.length}: [${tools.join(", ")}]`);
238
+ if (events.length !== expectedTools.length) {
239
+ throw new Error(`Expected ${expectedTools.length} "${event}" events but got ${events.length}: [${tools.join(", ")}]`);
234
240
  }
235
241
  }
236
242
  // ─── Environment Helpers ───────────────────────────────────────────
@@ -244,12 +250,28 @@ function setOryEnv(overrides = {}) {
244
250
  ORY_PROJECT_URL: "projectUrl" in overrides
245
251
  ? overrides.projectUrl
246
252
  : "https://test.projects.oryapis.com",
247
- ORY_SESSION_TOKEN: "sessionToken" in overrides ? overrides.sessionToken : "valid-token",
248
- ORY_OAUTH2_TOKEN: overrides.oauth2Token,
253
+ ORY_AGENT_SECURITY_URL: "agentSecurityUrl" in overrides
254
+ ? overrides.agentSecurityUrl
255
+ : "https://agents.console.ory.com",
256
+ // Both connection values default to present: tests exercise the
257
+ // *connected* posture unless they explicitly clear one.
258
+ ORY_OAUTH2_CLIENT_ID: "oauth2ClientId" in overrides ? overrides.oauth2ClientId : "test-login-client",
259
+ ORY_USER_OAUTH2_TOKEN: "userToken" in overrides ? overrides.userToken : "valid-token",
249
260
  ORY_AGENT_SUBJECT_ID: overrides.subjectId,
250
261
  ORY_PERMISSION_NAMESPACE: overrides.namespace,
251
- ORY_PERMISSION_MODE: "permissionMode" in overrides ? overrides.permissionMode : "enforce",
262
+ // Disable the mode cache's TTL so each gate re-reads the stub — no stale
263
+ // memo bleeds across tests.
264
+ ORY_PERMISSION_MODE_TTL_MS: "0",
252
265
  };
266
+ const prevMode = currentTestMode;
267
+ setTestPermissionMode("permissionMode" in overrides ? overrides.permissionMode : "enforce");
268
+ // Isolate the config dir too. The mode is now server-read and the session-start
269
+ // warm persists it to config.json; without isolation that warm would write the
270
+ // developer's real ~/.config and bleed between tests. A test that also calls
271
+ // useTempConfigDir just nests another temp dir on top (both restore cleanly).
272
+ const prevXdg = process.env.XDG_CONFIG_HOME;
273
+ const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "ory-env-config-"));
274
+ process.env.XDG_CONFIG_HOME = tmpHome;
253
275
  for (const [key, value] of Object.entries(vars)) {
254
276
  saved[key] = process.env[key];
255
277
  if (value !== undefined) {
@@ -260,6 +282,7 @@ function setOryEnv(overrides = {}) {
260
282
  }
261
283
  }
262
284
  return () => {
285
+ setTestPermissionMode(prevMode);
263
286
  for (const [key, value] of Object.entries(saved)) {
264
287
  if (value !== undefined) {
265
288
  process.env[key] = value;
@@ -268,6 +291,11 @@ function setOryEnv(overrides = {}) {
268
291
  delete process.env[key];
269
292
  }
270
293
  }
294
+ if (prevXdg !== undefined)
295
+ process.env.XDG_CONFIG_HOME = prevXdg;
296
+ else
297
+ delete process.env.XDG_CONFIG_HOME;
298
+ fs.rmSync(tmpHome, { recursive: true, force: true });
271
299
  };
272
300
  }
273
301
  /**
@@ -277,7 +305,7 @@ function setOryEnv(overrides = {}) {
277
305
  * that restores the env var and removes the temp directory.
278
306
  *
279
307
  * Use `saveConfig(...)` from `./config.js` inside the test to shape the
280
- * isolated config (e.g. `saveConfig({ auditOnly: true })`).
308
+ * isolated config (e.g. `saveConfig({ projectUrl, oauth2ClientId })`).
281
309
  */
282
310
  function useTempConfigDir() {
283
311
  const saved = process.env.XDG_CONFIG_HOME;
@@ -291,21 +319,65 @@ function useTempConfigDir() {
291
319
  fs.rmSync(tmpHome, { recursive: true, force: true });
292
320
  };
293
321
  }
322
+ /** Placeholder values `connectSecurity` / `disconnectSecurity` write. */
323
+ exports.TEST_PROJECT_URL = "https://test.projects.oryapis.com";
324
+ exports.TEST_AGENT_SECURITY_URL = "https://agents.console.ory.com";
325
+ exports.TEST_OAUTH2_CLIENT_ID = "test-login-client";
326
+ /**
327
+ * Put the isolated config into the **connected** state so the auth gates and
328
+ * permission checks actually run.
329
+ *
330
+ * Agent Security is connected exactly when a project URL and an OAuth2 client
331
+ * id both resolve — there is no flag to set. Tests that exercise checks need
332
+ * both, and writing them through one named call keeps that requirement in one
333
+ * place instead of two literals in every `beforeEach`.
334
+ *
335
+ * Call after `useTempConfigDir()` so it writes to the temp config.
336
+ */
337
+ function connectSecurity(overrides = {}) {
338
+ (0, config_js_1.saveConfig)({
339
+ projectUrl: overrides.projectUrl ?? exports.TEST_PROJECT_URL,
340
+ agentSecurityUrl: overrides.agentSecurityUrl ?? exports.TEST_AGENT_SECURITY_URL,
341
+ oauth2ClientId: overrides.oauth2ClientId ?? exports.TEST_OAUTH2_CLIENT_ID,
342
+ });
343
+ }
344
+ /**
345
+ * Put the isolated config into a partial connection state for contract tests.
346
+ *
347
+ * `which` selects which connection values are absent. The `no-client-id` case
348
+ * now exercises the reserved client default rather than a disconnected state.
349
+ * The `neither` and `no-project-url` forms remain disconnected.
350
+ */
351
+ function disconnectSecurity(which = "neither") {
352
+ (0, config_js_1.saveConfig)({
353
+ projectUrl: which === "no-client-id" ? exports.TEST_PROJECT_URL : null,
354
+ agentSecurityUrl: which === "no-client-id" ? exports.TEST_AGENT_SECURITY_URL : null,
355
+ oauth2ClientId: which === "no-project-url" ? exports.TEST_OAUTH2_CLIENT_ID : null,
356
+ });
357
+ // Env wins over the file, so a leaked env var would silently reconnect.
358
+ delete process.env.ORY_PROJECT_URL;
359
+ delete process.env.ORY_SDK_URL;
360
+ delete process.env.ORY_AGENT_SECURITY_URL;
361
+ delete process.env.ORY_OAUTH2_CLIENT_ID;
362
+ if (which === "no-client-id")
363
+ process.env.ORY_PROJECT_URL = exports.TEST_PROJECT_URL;
364
+ if (which === "no-project-url")
365
+ process.env.ORY_OAUTH2_CLIENT_ID = exports.TEST_OAUTH2_CLIENT_ID;
366
+ }
294
367
  /**
295
368
  * Clear all Ory env vars (simulate unconfigured state).
296
369
  */
297
370
  function clearOryEnv() {
298
371
  delete process.env.ORY_PROJECT_URL;
299
- delete process.env.ORY_API_KEY;
300
- delete process.env.ORY_SESSION_TOKEN;
301
- delete process.env.ORY_OAUTH2_TOKEN;
372
+ delete process.env.ORY_AGENT_SECURITY_URL;
373
+ delete process.env.ORY_OAUTH2_CLIENT_ID;
374
+ delete process.env.ORY_USER_OAUTH2_TOKEN;
302
375
  delete process.env.ORY_AGENT_SUBJECT_ID;
303
376
  delete process.env.ORY_USER_SUBJECT_ID;
304
377
  delete process.env.ORY_USER_SUBJECT_NAMESPACE;
305
378
  delete process.env.ORY_PERMISSION_NAMESPACE;
306
- // Intentionally leave ORY_PERMISSION_MODE alone it's typically set
307
- // once at the top of a test file to pin the historical enforce-mode
308
- // behavior, and unconfigured-Ory tests don't exercise permission
379
+ // The mode is a server-read permission now (no env var). Leave the stub's
380
+ // `currentTestMode` alone unconfigured-Ory tests don't exercise permission
309
381
  // checks anyway.
310
382
  }
311
383
  // ─── Stub Presets ──────────────────────────────────────────────────
@@ -337,55 +409,100 @@ function stubOAuth2Success(client) {
337
409
  function stubOAuth2Inactive(client) {
338
410
  return stubApi(client, "oauth2", "introspectOAuth2Token", () => Promise.resolve(exports.MOCK_INACTIVE_OAUTH2_RESPONSE));
339
411
  }
412
+ // ─── Permission mode (server-read) test control ────────────────────
413
+ //
414
+ // The gate now resolves observe/enforce by issuing a `checkPermission` against
415
+ // the reserved `__ory_permission_mode__#enforce` object (see permission-mode.ts).
416
+ // So the generic checkPermission stubs must answer THAT check with the desired
417
+ // mode and every OTHER check with the tool decision. `currentTestMode` holds the
418
+ // mode a test wants (default `enforce`, preserving the historical default where
419
+ // `setOryEnv` pinned enforce so deny-path assertions blocked).
420
+ let currentTestMode = "enforce";
421
+ /**
422
+ * Set the mode the server-read stub reports. Also clears the in-memory
423
+ * permission-mode cache so the new value takes effect immediately (tests run
424
+ * with `ORY_PERMISSION_MODE_TTL_MS=0`, but resetting is belt-and-suspenders).
425
+ */
426
+ function setTestPermissionMode(mode) {
427
+ currentTestMode = mode;
428
+ (0, permission_mode_js_1.resetPermissionModeCache)();
429
+ }
430
+ /** True when a checkPermission's params target the permission-mode object. */
431
+ function isModeCheck(args) {
432
+ const p = args[0];
433
+ return p?.object === opl_js_1.PERMISSION_MODE_OBJECT && p?.relation === opl_js_1.RELATION_ENFORCED_SUBJECTS;
434
+ }
435
+ /**
436
+ * Build a `checkPermission` impl that answers the mode check with
437
+ * `currentTestMode` and every other check with `toolResponse`. Every stub below
438
+ * routes through this so a single test setting controls the mode independently
439
+ * of the tool decision.
440
+ */
441
+ function checkPermissionResponder(toolResponse) {
442
+ return (...args) => {
443
+ if (isModeCheck(args)) {
444
+ return Promise.resolve(currentTestMode === "enforce" ? exports.PERMISSION_ALLOWED : exports.PERMISSION_DENIED);
445
+ }
446
+ return Promise.resolve(toolResponse);
447
+ };
448
+ }
449
+ function ensurePermissionCredential(client) {
450
+ if (client.agentPrincipal.token)
451
+ return;
452
+ client.setAgentPrincipal({ ...client.agentPrincipal, token: "test-agent-token" });
453
+ }
340
454
  /** Stub checkPermission to allow. */
341
455
  function stubPermissionAllowed(client) {
342
- return stubApi(client, "permission", "checkPermission", () => Promise.resolve(exports.PERMISSION_ALLOWED));
456
+ ensurePermissionCredential(client);
457
+ stubApi(client, "permission", "batchCheckPermission", (request) => {
458
+ const tuples = request.batchCheckPermissionBody?.tuples ?? [];
459
+ return Promise.resolve({
460
+ data: {
461
+ results: tuples.map((tuple) => ({
462
+ // "Allowed" means the requested action is granted; an explicit
463
+ // block probe must remain absent or it would reverse that decision.
464
+ allowed: tuple.relation !== "blockedSubjects",
465
+ })),
466
+ },
467
+ });
468
+ });
469
+ return stubApi(client, "permission", "checkPermission", checkPermissionResponder(exports.PERMISSION_ALLOWED));
343
470
  }
344
- /** Stub checkPermission to deny. */
471
+ /** Stub checkPermission to deny (the tool check; the mode check still answers `currentTestMode`). */
345
472
  function stubPermissionDenied(client) {
346
- return stubApi(client, "permission", "checkPermission", () => Promise.resolve(exports.PERMISSION_DENIED));
473
+ ensurePermissionCredential(client);
474
+ return stubApi(client, "permission", "checkPermission", checkPermissionResponder(exports.PERMISSION_DENIED));
347
475
  }
348
476
  /** Stub checkPermission to throw a network error. */
349
477
  function stubPermissionNetworkError(client) {
478
+ ensurePermissionCredential(client);
350
479
  return stubApi(client, "permission", "checkPermission", () => Promise.reject(makeNetworkError()));
351
480
  }
352
481
  /** Stub checkPermission to throw a rate-limit error. */
353
482
  function stubPermissionRateLimited(client) {
483
+ ensurePermissionCredential(client);
354
484
  return stubApi(client, "permission", "checkPermission", () => Promise.reject(makeRateLimitError()));
355
485
  }
356
- /**
357
- * Spy on the public `createRelationship` method and resolve successfully.
358
- * Prefer this over stubbing the internal relationship API: `setAgentPrincipal`
359
- * rebuilds the API instances when the agent token changes (e.g. inside
360
- * `sessionStart`), which silently discards API-level stubs.
361
- */
362
- function spyRelationshipCreated(client) {
363
- return vitest_1.vi
364
- .spyOn(client, "createRelationship")
365
- .mockResolvedValue({ created: true, alreadyExisted: false });
366
- }
367
- /** Spy on the public `createRelationship` method and reject. */
368
- function spyRelationshipCreateError(client) {
369
- return vitest_1.vi
370
- .spyOn(client, "createRelationship")
371
- .mockRejectedValue(new Error("relationship write failed"));
372
- }
373
486
  // ─── MCP Stub Presets ─────────────────────────────────────────────
374
487
  /** Stub both checkPermission (server allow) and batchCheckPermission (both allow). */
375
488
  function stubMcpAllowed(client) {
376
- stubApi(client, "permission", "checkPermission", () => Promise.resolve(exports.PERMISSION_ALLOWED));
489
+ ensurePermissionCredential(client);
490
+ stubApi(client, "permission", "checkPermission", checkPermissionResponder(exports.PERMISSION_ALLOWED));
377
491
  return stubApi(client, "permission", "batchCheckPermission", () => Promise.resolve(exports.BATCH_BOTH_ALLOWED));
378
492
  }
379
493
  /** Stub checkPermission to deny (server-only MCP check). */
380
494
  function stubMcpServerDenied(client) {
381
- return stubApi(client, "permission", "checkPermission", () => Promise.resolve(exports.PERMISSION_DENIED));
495
+ ensurePermissionCredential(client);
496
+ return stubApi(client, "permission", "checkPermission", checkPermissionResponder(exports.PERMISSION_DENIED));
382
497
  }
383
498
  /** Stub batchCheckPermission: server allowed, tool denied. */
384
499
  function stubMcpToolDenied(client) {
385
- stubApi(client, "permission", "checkPermission", () => Promise.resolve(exports.PERMISSION_ALLOWED));
500
+ ensurePermissionCredential(client);
501
+ stubApi(client, "permission", "checkPermission", checkPermissionResponder(exports.PERMISSION_ALLOWED));
386
502
  return stubApi(client, "permission", "batchCheckPermission", () => Promise.resolve(exports.BATCH_SERVER_ALLOWED_TOOL_DENIED));
387
503
  }
388
504
  /** Stub checkPermission to throw network error (MCP fail-open). */
389
505
  function stubMcpNetworkError(client) {
506
+ ensurePermissionCredential(client);
390
507
  return stubApi(client, "permission", "checkPermission", () => Promise.reject(makeNetworkError()));
391
508
  }
@@ -1,15 +1,14 @@
1
1
  /**
2
- * Canonical per-harness tool catalog used by `permissions bootstrap` to
3
- * seed Keto tuples granting the current user `use` on each tool.
2
+ * Canonical per-harness tool catalog: the objects an Ory project grants `use`
3
+ * on, and the set `permissions status` probes coverage across.
4
4
  *
5
5
  * These lists cover each harness's built-in tools — the names the harness
6
6
  * passes to the pre-tool-use hook. MCP server tools are intentionally
7
7
  * omitted because they're discovered dynamically per session and can't be
8
- * enumerated at bootstrap time.
8
+ * enumerated ahead of time.
9
9
  *
10
- * The lists are best-effort: harness vendors add and rename tools over
11
- * time. `bootstrap` is idempotent and additive, so re-running it after a
12
- * catalog update is safe.
10
+ * The lists are best-effort: harness vendors add and rename tools over time,
11
+ * so a project's grants may cover more or fewer names than the catalog knows.
13
12
  */
14
13
  /**
15
14
  * Built-in tool names known to ship with each supported harness.
@@ -27,6 +26,7 @@ export declare const HARNESS_TOOL_CATALOG: {
27
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"];
28
27
  readonly pi: readonly ["read", "write", "edit", "bash", "grep", "find", "ls"];
29
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"];
29
+ readonly cursor: readonly ["Read", "Write", "Edit", "Delete", "Shell", "List", "Search", "MCP", "Task"];
30
30
  };
31
31
  /** Names of the harnesses with a known tool catalog. */
32
32
  export type KnownHarness = keyof typeof HARNESS_TOOL_CATALOG;
@@ -34,13 +34,12 @@ export declare const KNOWN_HARNESSES: readonly KnownHarness[];
34
34
  /**
35
35
  * Tools for a specific harness. Returns the canonical list when known;
36
36
  * empty array for unknown harnesses (callers can fall back to {@link
37
- * ALL_TOOLS} or skip bootstrap with a warning).
37
+ * ALL_TOOLS} or skip the catalog-scoped step with a warning).
38
38
  */
39
39
  export declare function getToolCatalog(harness: string): readonly string[];
40
40
  /**
41
- * Union of every known tool across all harnesses, deduplicated. Used by
42
- * the dev launcher's seed routine, which grants the local user identity
43
- * broad access so the same launcher can run every harness end-to-end.
41
+ * Union of every known tool across all harnesses, deduplicated. Used by local
42
+ * environment seeding to grant broad access across every built-in tool set.
44
43
  */
45
44
  export declare const ALL_TOOLS: readonly string[];
46
45
  /**
@@ -53,7 +52,7 @@ export declare const ALL_TOOLS: readonly string[];
53
52
  * the user is about to handle directly.
54
53
  *
55
54
  * Plugins consult this list in their pre-tool gate and short-circuit to
56
- * a single `user.interaction` audit span when a match is found. See
55
+ * a single `user.interaction` audit event when a match is found. See
57
56
  * `gateToolCall` in `permissions.ts`.
58
57
  *
59
58
  * The list is best-effort and additive: operators can extend it at
@@ -74,3 +73,46 @@ export declare function getInteractiveToolCatalog(harness: string): readonly str
74
73
  * env-var extension still applies.
75
74
  */
76
75
  export declare function isInteractiveTool(harness: string, toolName: string): boolean;
76
+ /**
77
+ * Per-harness name(s) of the shell/exec tool whose input is decomposed into
78
+ * `ShellTool:<word>#use` sub-checks. Validated against each harness's
79
+ * types/handlers. Names vary (`Bash`, `shell`, `exec`, `run_command`, …) so a
80
+ * precise map is used rather than the {@link isBashLikeTool} heuristic.
81
+ */
82
+ export declare const SHELL_TOOL_CATALOG: Record<string, readonly string[]>;
83
+ /**
84
+ * Shell-tool names for a harness, merged with the operator env extension
85
+ * `ORY_SHELL_TOOLS` (same pattern as `ORY_INTERACTIVE_TOOLS`) so a vendor tool
86
+ * rename can be handled without a plugin release.
87
+ */
88
+ export declare function getShellToolCatalog(harness: string): readonly string[];
89
+ /** Is the named tool, for this harness, the shell/exec tool to decompose? */
90
+ export declare function isShellTool(harness: string, toolName: string): boolean;
91
+ /**
92
+ * Broad list of shell command *words* — common dev binaries, sensitive
93
+ * binaries, and builtins — that an Ory project is expected to grant `use` on
94
+ * so enforce mode is practical out of the box; revoke a specific word in the
95
+ * Ory Console to close it. Builtins are included because they are a real
96
+ * bypass surface (`echo "$SECRET" >/dev/tcp/…`, `read`, redirection tricks).
97
+ * `permissions status` reports coverage against this list.
98
+ */
99
+ export declare const SHELL_COMMAND_CATALOG: readonly string[];
100
+ /**
101
+ * The shell command word list, merged with the operator env extension
102
+ * `ORY_SHELL_COMMANDS`.
103
+ */
104
+ export declare function getShellCommandCatalog(): readonly string[];
105
+ /**
106
+ * Keto namespace for shell-command sub-tools. Defaults to `ShellTool`,
107
+ * parallel to `mcp_servers` / `mcp_tools`. The object is the bare word (`curl`),
108
+ * relation `use`. Override with `ORY_SHELL_COMMAND_NAMESPACE`.
109
+ */
110
+ export declare function resolveShellCommandNamespace(): string;
111
+ /**
112
+ * Extract the shell command string from a harness's tool-args object — the same
113
+ * object the handler already passes to `summarizeToolInput`. Returns `undefined`
114
+ * when the tool isn't a shell tool for this harness or no command field is
115
+ * present (the gate then skips decomposition and degrades to the top-level
116
+ * `Bash` check — it never breaks).
117
+ */
118
+ export declare function extractShellCommand(harness: string, toolName: string, args: unknown): string | undefined;