@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
@@ -0,0 +1,333 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.probePermissionsCoverage = probePermissionsCoverage;
37
+ exports.resolveDynamicName = resolveDynamicName;
38
+ exports.collectStatusReport = collectStatusReport;
39
+ /**
40
+ * Structured status data — the data-gathering half of the `status` command,
41
+ * shared by the text renderer ({@link file://./status-cli.ts}) and the local
42
+ * web dashboard. `collectStatusReport(harness)` returns a plain, serializable
43
+ * object mirroring every section `status` prints (configuration, user identity,
44
+ * agent identity, permissions coverage, recent activity), so the two surfaces
45
+ * never drift.
46
+ *
47
+ * The live permission-coverage probe ({@link probePermissionsCoverage}) lives
48
+ * here too and is imported back by `status-cli.ts`, so there is a single probe
49
+ * implementation.
50
+ */
51
+ const config_js_1 = require("./config.js");
52
+ const permission_mode_js_1 = require("./permission-mode.js");
53
+ const permissions_js_1 = require("./permissions.js");
54
+ const auth_store_js_1 = require("./auth-store.js");
55
+ const agent_auth_js_1 = require("./agent-auth.js");
56
+ const subject_js_1 = require("./subject.js");
57
+ const client_js_1 = require("./client.js");
58
+ const subject_js_2 = require("./subject.js");
59
+ const branding_js_1 = require("./branding.js");
60
+ const auth_js_1 = require("./auth.js");
61
+ const node_os_1 = require("node:os");
62
+ const tool_catalog_js_1 = require("./tool-catalog.js");
63
+ const opl_js_1 = require("./opl.js");
64
+ const fs = __importStar(require("node:fs"));
65
+ // Additive dashboard section: core Ory service health (see status-system.ts).
66
+ const status_system_js_1 = require("./status-system.js");
67
+ function resolveNamespace() {
68
+ return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTool";
69
+ }
70
+ /**
71
+ * Probe the harness's built-in tool catalog against Ory, returning how many
72
+ * tools the resolved user subject is allowed / denied / errored on, plus the
73
+ * live permission mode and the subject that was checked. Returns undefined when
74
+ * a probe can't run (no project URL, empty catalog, no user identity). Never
75
+ * throws.
76
+ *
77
+ * Checks route through {@link resolveCheckRelation}, i.e. the `access` permit —
78
+ * the same relation the runtime gate evaluates — so a tool carrying an explicit
79
+ * block is reported as denied here too, exactly as a tool call would be.
80
+ */
81
+ async function probePermissionsCoverage(harness) {
82
+ const resolved = (0, config_js_1.resolveConfig)();
83
+ if (!resolved.projectUrl)
84
+ return undefined;
85
+ const catalog = (0, tool_catalog_js_1.getToolCatalog)(harness);
86
+ if (catalog.length === 0)
87
+ return undefined;
88
+ const tokens = (0, auth_store_js_1.loadTokens)();
89
+ const subjectOverride = process.env.ORY_USER_SUBJECT_ID;
90
+ if (!subjectOverride && (!tokens || (0, auth_store_js_1.isExpired)(tokens))) {
91
+ return undefined;
92
+ }
93
+ const client = client_js_1.OryAgentClient.fromEnv(harness);
94
+ if (tokens && !(0, auth_store_js_1.isExpired)(tokens)) {
95
+ client.setUserPrincipal({ subject: tokens.subject, token: tokens.accessToken });
96
+ }
97
+ await (0, agent_auth_js_1.ensureAgentIdentity)(client, { projectUrl: resolved.projectUrl }).catch(() => {
98
+ /* best-effort; the probe still runs with whatever credentials we have */
99
+ });
100
+ const subject = (0, subject_js_2.resolveUserSubject)(client);
101
+ const subjectId = (0, subject_js_2.subjectLabel)(subject);
102
+ if (subjectId === "agent:unknown")
103
+ return undefined;
104
+ const agentSubject = (0, subject_js_2.resolveAgentSubject)(client);
105
+ const live = await (0, permission_mode_js_1.resolvePermissionMode)(client, subject, {
106
+ additionalSubjects: agentSubject ? [{ scope: "agent", subject: agentSubject }] : [],
107
+ });
108
+ const namespace = resolveNamespace();
109
+ const relation = (0, permissions_js_1.resolveCheckRelation)("users");
110
+ const result = {
111
+ total: catalog.length,
112
+ allowed: 0,
113
+ denied: 0,
114
+ errored: 0,
115
+ mode: live.mode,
116
+ modeSource: live.source,
117
+ subject: subjectId,
118
+ };
119
+ for (const tool of catalog) {
120
+ const check = { namespace, object: tool, relation, ...subject };
121
+ try {
122
+ const probe = await client.checkPermission(check, {
123
+ activityAttributes: { toolName: tool, source: "status_coverage" },
124
+ });
125
+ if (probe.allowed)
126
+ result.allowed++;
127
+ else
128
+ result.denied++;
129
+ }
130
+ catch (err) {
131
+ const ory = err;
132
+ result.errored++;
133
+ result.errorCode = result.errorCode ?? ory.code ?? "unknown";
134
+ }
135
+ }
136
+ return result;
137
+ }
138
+ function collectConfig(harness) {
139
+ const r = (0, config_js_1.resolveConfig)();
140
+ const configPath = (0, config_js_1.getConfigPath)();
141
+ return {
142
+ configPath,
143
+ configExists: fs.existsSync(configPath),
144
+ projectUrl: r.projectUrl,
145
+ projectUrlSource: r.projectUrlSource,
146
+ projectId: r.projectId,
147
+ projectIdSource: r.projectIdSource,
148
+ projectName: r.projectName,
149
+ workspaceName: r.workspaceName,
150
+ oauth2ClientId: r.oauth2ClientId,
151
+ oauth2ClientIdSource: r.oauth2ClientIdSource,
152
+ oauth2ClientName: (0, branding_js_1.isUserLoginClient)({ clientId: r.oauth2ClientId })
153
+ ? branding_js_1.USER_LOGIN_CLIENT_NAME
154
+ : undefined,
155
+ security: r.security,
156
+ permissionMode: r.permissionMode,
157
+ permissionModeSource: r.permissionModeSource,
158
+ userSubjectNamespace: r.userSubjectNamespace,
159
+ userSubjectNamespaceSource: r.userSubjectNamespaceSource,
160
+ namespace: resolveNamespace(),
161
+ principalNamespaces: [...opl_js_1.PRINCIPAL_NAMESPACES],
162
+ debugEnabled: process.env.ORY_AGENT_DEBUG === "true",
163
+ };
164
+ }
165
+ function collectUser() {
166
+ const tokens = (0, auth_store_js_1.loadTokens)();
167
+ if (!tokens) {
168
+ return { tokenCache: "empty" };
169
+ }
170
+ const nowSec = Math.floor(Date.now() / 1000);
171
+ return {
172
+ tokenCache: (0, auth_store_js_1.isExpired)(tokens) ? "stale" : "present",
173
+ subject: tokens.subject,
174
+ displayName: tokens.displayName ?? (0, auth_js_1.displayNameFromIdToken)(tokens.idToken),
175
+ expiresInSeconds: tokens.expiresAt - nowSec,
176
+ clientId: tokens.clientId,
177
+ };
178
+ }
179
+ /**
180
+ * Human-readable name for a DCR agent / sub-agent credential: the persisted
181
+ * `client_name` when present, else a best-effort reconstruction from the
182
+ * harness and (for sub-agents) type. Reconstruction uses the current hostname,
183
+ * which may differ from the registration host for credentials saved before the
184
+ * name was persisted — acceptable for a display-only label.
185
+ */
186
+ function resolveDynamicName(creds, subAgentType, harness) {
187
+ return (creds.clientName ??
188
+ (0, branding_js_1.agentClientName)({
189
+ harness: creds.harness ?? harness,
190
+ host: (0, node_os_1.hostname)() || "unknown-host",
191
+ subAgentType,
192
+ }));
193
+ }
194
+ function collectAgent(harness, sessionKey) {
195
+ if (process.env.ORY_AGENT_CLIENT_ID && process.env.ORY_AGENT_CLIENT_SECRET) {
196
+ return {
197
+ source: "static_client_credentials",
198
+ clientId: process.env.ORY_AGENT_CLIENT_ID,
199
+ subject: (0, subject_js_2.readAgentSubjectOverride)() ?? process.env.ORY_AGENT_CLIENT_ID,
200
+ };
201
+ }
202
+ const selected = sessionKey
203
+ ? (() => {
204
+ const credentials = (0, agent_auth_js_1.loadAgentDynamicCredentials)(harness, sessionKey);
205
+ return credentials ? { sessionKey, credentials } : undefined;
206
+ })()
207
+ : (0, agent_auth_js_1.latestAgentRegistration)(harness);
208
+ if (!selected)
209
+ return { source: "unregistered" };
210
+ const { sessionKey: credentialSession, credentials: persisted } = selected;
211
+ const resolved = (0, config_js_1.resolveConfig)();
212
+ const mismatch = resolved.projectUrl && persisted.projectUrl !== resolved.projectUrl
213
+ ? { registeredAgainst: persisted.projectUrl, current: resolved.projectUrl }
214
+ : undefined;
215
+ const subject = (0, subject_js_2.readAgentSubjectOverride)() ?? persisted.clientId;
216
+ return {
217
+ source: "dcr",
218
+ name: resolveDynamicName(persisted, undefined, harness),
219
+ sessionKey: credentialSession,
220
+ ...(credentialSession === config_js_1.SESSIONLESS_KEY
221
+ ? {}
222
+ : { sessionSubject: `${subject_js_1.AGENT_NAMESPACE}:${subject}|${credentialSession}` }),
223
+ clientId: persisted.clientId,
224
+ subject,
225
+ registeredAt: new Date(persisted.registeredAt * 1000).toISOString(),
226
+ projectUrlMismatch: mismatch,
227
+ };
228
+ }
229
+ function collectSubAgents(harness, requestedSession) {
230
+ const sessionKey = requestedSession
231
+ ?? (0, agent_auth_js_1.latestAgentRegistration)(harness)?.sessionKey
232
+ ?? (0, agent_auth_js_1.latestSubAgentSession)(harness);
233
+ const persisted = sessionKey ? (0, agent_auth_js_1.loadAllSubAgentDynamicCredentials)(harness, sessionKey) : {};
234
+ const resolved = (0, config_js_1.resolveConfig)();
235
+ return Object.entries(persisted)
236
+ .map(([subAgentType, creds]) => {
237
+ const mismatch = resolved.projectUrl && creds.projectUrl !== resolved.projectUrl
238
+ ? { registeredAgainst: creds.projectUrl, current: resolved.projectUrl }
239
+ : undefined;
240
+ return {
241
+ subAgentType,
242
+ name: resolveDynamicName(creds, subAgentType, harness),
243
+ clientId: creds.clientId,
244
+ subject: creds.clientId,
245
+ registeredAt: new Date(creds.registeredAt * 1000).toISOString(),
246
+ delegatedBy: creds.delegation?.delegatedBy,
247
+ projectUrlMismatch: mismatch,
248
+ };
249
+ })
250
+ .sort((a, b) => a.subAgentType.localeCompare(b.subAgentType));
251
+ }
252
+ async function collectPermissions(harness) {
253
+ const resolved = (0, config_js_1.resolveConfig)();
254
+ const namespace = resolveNamespace();
255
+ const catalog = (0, tool_catalog_js_1.getToolCatalog)(harness);
256
+ // Falls back to the last-known cached mode on every path that can't reach the
257
+ // project to read it live.
258
+ const base = {
259
+ mode: resolved.permissionMode,
260
+ modeSource: resolved.permissionModeSource,
261
+ namespace,
262
+ };
263
+ // One reason covers both missing values: with either absent no check can run.
264
+ if (!resolved.security.connected)
265
+ return { ...base, coverageStatus: "not_connected" };
266
+ if (catalog.length === 0) {
267
+ return { ...base, coverageStatus: "no_catalog", knownHarnesses: [...tool_catalog_js_1.KNOWN_HARNESSES] };
268
+ }
269
+ const hasUser = !!process.env.ORY_USER_SUBJECT_ID || isUserTokenUsable();
270
+ if (!hasUser)
271
+ return { ...base, coverageStatus: "no_user" };
272
+ const coverage = await probePermissionsCoverage(harness);
273
+ if (!coverage)
274
+ return { ...base, coverageStatus: "no_user" };
275
+ // The probe read the mode live; prefer it over the cached config value.
276
+ const withMode = { ...base, mode: coverage.mode, modeSource: coverage.modeSource };
277
+ if (coverage.errored > 0 && coverage.allowed === 0 && coverage.denied === 0) {
278
+ return { ...withMode, coverageStatus: "probe_failed", coverage };
279
+ }
280
+ const status = coverage.allowed === coverage.total
281
+ ? "fully_granted"
282
+ : coverage.allowed === 0
283
+ ? "no_grants"
284
+ : "partial";
285
+ return { ...withMode, coverageStatus: status, coverage };
286
+ }
287
+ function isUserTokenUsable() {
288
+ const tokens = (0, auth_store_js_1.loadTokens)();
289
+ return !!tokens && !(0, auth_store_js_1.isExpired)(tokens);
290
+ }
291
+ function readTail(file, limit) {
292
+ if (!file || !fs.existsSync(file))
293
+ return { file, count: 0, recent: [] };
294
+ const lines = fs.readFileSync(file, "utf-8").trim().split("\n").filter(Boolean);
295
+ const recent = lines.slice(-limit).map((raw) => {
296
+ try {
297
+ return { raw, parsed: JSON.parse(raw) };
298
+ }
299
+ catch {
300
+ return { raw };
301
+ }
302
+ });
303
+ return { file, count: lines.length, recent };
304
+ }
305
+ function collectActivity(harness) {
306
+ const log = readTail((0, client_js_1.resolveDebugLogPath)(harness), 10);
307
+ return {
308
+ logFile: log.file,
309
+ logCount: log.count,
310
+ recentEvents: log.recent,
311
+ };
312
+ }
313
+ /**
314
+ * Gather the full structured status report for a harness. The permissions
315
+ * section runs a live network probe when a project URL and user identity are
316
+ * available (same conditions as the text `status` command).
317
+ *
318
+ * `sessionKey` is the run being reported on, when the caller is inside one. The
319
+ * standalone CLI passes none, so the report selects the harness's newest stored
320
+ * session rather than inventing an ambient session.
321
+ */
322
+ async function collectStatusReport(harness, sessionKey) {
323
+ return {
324
+ harness,
325
+ config: collectConfig(harness),
326
+ user: collectUser(),
327
+ agent: collectAgent(harness, sessionKey),
328
+ subAgents: collectSubAgents(harness, sessionKey),
329
+ permissions: await collectPermissions(harness),
330
+ activity: collectActivity(harness),
331
+ system: await (0, status_system_js_1.collectSystemServices)(),
332
+ };
333
+ }
@@ -0,0 +1,24 @@
1
+ export interface StatusSystemService {
2
+ /** Service name, e.g. "Kratos". */
3
+ name: string;
4
+ /** One-line role, e.g. "Identities & sessions". */
5
+ role: string;
6
+ /** Published host port. */
7
+ port: number;
8
+ /** Base URL for the service. */
9
+ url: string;
10
+ /** The endpoint probed to decide health. */
11
+ healthUrl: string;
12
+ /** true = healthy, false = unreachable/error, null = not probed. */
13
+ healthy: boolean | null;
14
+ /** Human-readable detail (HTTP status or error) when unhealthy. */
15
+ detail?: string;
16
+ }
17
+ export interface StatusSystemSection {
18
+ services: StatusSystemService[];
19
+ }
20
+ /**
21
+ * Probe the core Ory services' readiness endpoints and return their health.
22
+ * Never throws — an unreachable service is reported as `healthy: false`.
23
+ */
24
+ export declare function collectSystemServices(): Promise<StatusSystemSection>;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.collectSystemServices = collectSystemServices;
4
+ /**
5
+ * System-information probe for the dashboard: the health of the core Ory
6
+ * backend services the plugin authenticates and authorizes against — Kratos
7
+ * (identities), Hydra (OAuth2), and Keto (permissions).
8
+ *
9
+ * This is additive to the shared {@link file://./status-data.ts} report: the
10
+ * dashboard renders it as a "System information" section, while the installer
11
+ * simply ignores it. Ports and health endpoints are the same constants the
12
+ * local-stack manager and `local status` use, so the surfaces never disagree.
13
+ */
14
+ const configs_js_1 = require("./local/configs.js");
15
+ /** The three core Ory services, in display order. */
16
+ function systemServiceSpecs() {
17
+ return [
18
+ { name: "Kratos", role: "Identities & sessions", port: configs_js_1.KRATOS_PUBLIC_PORT, healthPath: "/health/ready" },
19
+ { name: "Hydra", role: "OAuth2 & OIDC", port: configs_js_1.HYDRA_PUBLIC_PORT, healthPath: "/health/ready" },
20
+ { name: "Keto", role: "Permissions (Zanzibar relation checks)", port: configs_js_1.KETO_READ_PORT, healthPath: "/health/ready" },
21
+ ];
22
+ }
23
+ async function probe(healthUrl) {
24
+ try {
25
+ const res = await fetch(healthUrl, { signal: AbortSignal.timeout(2500) });
26
+ if (res.ok)
27
+ return { healthy: true };
28
+ return { healthy: false, detail: `HTTP ${res.status}` };
29
+ }
30
+ catch (err) {
31
+ return { healthy: false, detail: err instanceof Error ? err.message : String(err) };
32
+ }
33
+ }
34
+ /**
35
+ * Probe the core Ory services' readiness endpoints and return their health.
36
+ * Never throws — an unreachable service is reported as `healthy: false`.
37
+ */
38
+ async function collectSystemServices() {
39
+ const specs = systemServiceSpecs();
40
+ const services = await Promise.all(specs.map(async (spec) => {
41
+ const url = `http://localhost:${spec.port}`;
42
+ const healthUrl = `${url}${spec.healthPath}`;
43
+ const { healthy, detail } = await probe(healthUrl);
44
+ const service = {
45
+ name: spec.name,
46
+ role: spec.role,
47
+ port: spec.port,
48
+ url,
49
+ healthUrl,
50
+ healthy,
51
+ detail,
52
+ };
53
+ return service;
54
+ }));
55
+ return { services };
56
+ }
package/dist/subject.d.ts CHANGED
@@ -1,24 +1,34 @@
1
1
  /**
2
2
  * User-subject resolution for permission checks.
3
3
  *
4
- * Permission checks need a subject reference that matches the form
5
- * stored in Keto. Two shapes are supported:
4
+ * Every principal user, agent, sub-agent is addressed as a **SubjectSet**
5
+ * `<namespace>:<object>` (e.g. `User:<UUID>`). This is the form the Ory Console's
6
+ * *Add relationship* dialog writes, the form the local-stack seed writes, and the
7
+ * Zanzibar-idiomatic composition point (grant `Role:developer` `use` on a tool and
8
+ * add principals to the role, rather than fanning out a tuple per subject). It is
9
+ * the only first-class subject form: Keto does no cross-form expansion, so a check
10
+ * must match the write shape exactly, and standardizing on one shape removes that
11
+ * silent-misfire footgun.
6
12
  *
7
- * - **SubjectSet** — `<namespace>:<object>` (e.g. `User:<UUID>`). This
8
- * is the form the Console's *Add relationship* dialog writes, so
9
- * dev/demo flows that grant tuples through the UI use it.
10
- *
11
- * - **Direct SubjectID** opaque string (e.g. `user:<UUID>` or
12
- * `session:<id>`). Backward-compatible default when no subject
13
- * namespace is configured.
14
- *
15
- * The selection is driven by the user-subject namespace: `ORY_USER_SUBJECT_NAMESPACE`
16
- * takes precedence, then the client's configured `userSubjectNamespace` (persisted
17
- * in the shared config, e.g. by the local-stack install path). When a namespace is
18
- * present the resolver builds a SubjectSet using it; otherwise it falls back to the
19
- * direct SubjectID chain.
13
+ * The namespace is resolved by precedence: `ORY_USER_SUBJECT_NAMESPACE` (env)
14
+ * the client's configured `userSubjectNamespace` (persisted in the shared config)
15
+ * the {@link DEFAULT_USER_SUBJECT_NAMESPACE} (`User`). The env/config values now
16
+ * *override* the default namespace; they are no longer an opt-in switch that
17
+ * toggles between SubjectSet and a legacy direct SubjectID.
20
18
  */
21
19
  import type { OryAgentClient } from "./client.js";
20
+ /**
21
+ * Default namespace a resolved user principal is addressed under. The
22
+ * local-stack seed, the Console *Add relationship* dialog, and the Network OPL
23
+ * provisioning all use `User`, so it is the out-of-the-box default.
24
+ */
25
+ export declare const DEFAULT_USER_SUBJECT_NAMESPACE = "User";
26
+ /** Namespace the agent principal is addressed under (delegation subjects). */
27
+ export declare const AGENT_NAMESPACE = "Agent";
28
+ /** Namespace a sub-agent principal is addressed under. */
29
+ export declare const SUBAGENT_NAMESPACE = "SubAgent";
30
+ /** Namespace the no-user-identity `session:<id>` fallback is addressed under. */
31
+ export declare const SESSION_NAMESPACE = "Session";
22
32
  export type UserSubjectRef = {
23
33
  subjectId: string;
24
34
  } | {
@@ -33,7 +43,7 @@ export type UserSubjectRef = {
33
43
  * client's user principal and the env overrides in
34
44
  * {@link resolveUserSubject}. The override only applies within `fn`'s
35
45
  * async context, so concurrent calls cannot observe each other's subject.
36
- * `ORY_USER_SUBJECT_NAMESPACE` SubjectSet shaping still applies.
46
+ * SubjectSet shaping still applies.
37
47
  *
38
48
  * A missing/empty `subject` is a no-op: `fn` runs with the normal
39
49
  * resolution chain.
@@ -46,13 +56,109 @@ export declare function runWithUserSubject<T>(subject: string | undefined, fn: (
46
56
  * legacy `ORY_AGENT_SUBJECT_ID`, then the caller-supplied `fallback`
47
57
  * (typically `session:<id>`).
48
58
  *
49
- * Returns a SubjectSet when a user-subject namespace is configured (env or
50
- * client config) and a concrete subject is available; otherwise a direct
51
- * SubjectID.
59
+ * Always returns a SubjectSet. A resolved principal is addressed under the
60
+ * configured user namespace (`ORY_USER_SUBJECT_NAMESPACE` client config
61
+ * {@link DEFAULT_USER_SUBJECT_NAMESPACE}). A `session:`-prefixed fallback is
62
+ * addressed under {@link SESSION_NAMESPACE} (`Session:<id>`). Only when nothing
63
+ * resolves at all do we emit the direct `agent:unknown` sentinel — an error
64
+ * marker that must never match a stored tuple.
52
65
  */
53
66
  export declare function resolveUserSubject(client: OryAgentClient, fallback?: string): UserSubjectRef;
54
67
  /**
55
68
  * Printable label for a `UserSubjectRef`. Used in denial messages and
56
- * span attributes. SubjectSets render as `<namespace>:<object>`.
69
+ * activity attributes. SubjectSets render as `<namespace>:<object>`.
57
70
  */
58
71
  export declare function subjectLabel(ref: UserSubjectRef): string;
72
+ /** Separator between the credential client id and narrower scoping axes. */
73
+ export declare const SUBJECT_AXIS_SEPARATOR = "|";
74
+ /** The env var that overrides the agent's subject. */
75
+ export declare const AGENT_SUBJECT_ID_ENV_VAR = "ORY_AGENT_SUBJECT_ID";
76
+ /**
77
+ * Validate a subject override (issue #241).
78
+ *
79
+ * `deriveSubject` above already refuses to produce a trailing separator,
80
+ * because a subject ending in `|` silently never matches a stored relation.
81
+ * The same care is owed to the *base* it joins onto, which arrives from an
82
+ * operator-settable env var and was previously used verbatim.
83
+ *
84
+ * A value containing the axis separator is rejected for two independent
85
+ * reasons:
86
+ *
87
+ * 1. **It collapses the identity grains.** The credential, session, and spawn
88
+ * subjects are told apart only by separator position, so an override
89
+ * containing one makes them ambiguous: `override="a|b"` in session `c` and
90
+ * `override="a"` in session `b|c` produce the same string. A session-scoped
91
+ * block written to stop one runaway run could then match the base credential, or a
92
+ * different run — and all three grains are read together in one batched
93
+ * check, so this is a live semantics break rather than a theoretical one.
94
+ * 2. **It forges a delegation-node reference** (#225). A join key is exactly
95
+ * `<identity>|<harness>|<host>|<session>`, and the two string spaces stay
96
+ * disjoint today only because a Hydra client id is a UUID and contains no
97
+ * separator. This env var is the one supported knob that breaks that.
98
+ *
99
+ * Rejected rather than sanitized: silently stripping the separator would
100
+ * produce a *different* subject than the operator asked for, which fails just
101
+ * as confusingly and is harder to notice. Ignoring the value falls back to the
102
+ * client id — a real, working identity — and says so.
103
+ */
104
+ export declare function validateSubjectOverride(raw: string | undefined, envVar?: string): {
105
+ value?: string;
106
+ warning?: string;
107
+ };
108
+ /**
109
+ * Read and validate `ORY_AGENT_SUBJECT_ID`. Returns `undefined` when unset or
110
+ * rejected, pushing an explanation onto `warnings` in the latter case.
111
+ *
112
+ * One reader for every site that consumes the override, so the rule cannot hold
113
+ * in the resolver and not in what `status` reports.
114
+ */
115
+ export declare function readAgentSubjectOverride(env?: NodeJS.ProcessEnv, warnings?: string[]): string | undefined;
116
+ /**
117
+ * The registered agent credential subject: `Agent:<clientId>`. For DCR this
118
+ * client belongs to one session; static credentials may be shared explicitly.
119
+ *
120
+ * `undefined` when no agent principal is populated — the agent gate never
121
+ * blocks, so a session with no resolved machine identity is normal and callers
122
+ * must treat a missing agent subject as "nothing to check", not as a deny.
123
+ */
124
+ export declare function resolveAgentSubject(client: OryAgentClient): UserSubjectRef | undefined;
125
+ /**
126
+ * The **session-level** agent subject: `Agent:<clientId>|<session>`, unique to
127
+ * one run. Used for a block that stops the session's narrower subject without
128
+ * blocking its base credential subject.
129
+ *
130
+ * `undefined` when there is no agent principal, and — deliberately — also when
131
+ * there is no session. A caller with no session concept (an SDK integration in
132
+ * a long-running service) has exactly one sessionless credential subject;
133
+ * inventing a placeholder session would either collapse every run onto
134
+ * one string or produce a subject no admin could have written.
135
+ */
136
+ export declare function resolveAgentSessionSubject(client: OryAgentClient, sessionId?: string): UserSubjectRef | undefined;
137
+ /**
138
+ * The registered sub-agent credential subject: `SubAgent:<clientId>` for one
139
+ * typed sub-agent in one session.
140
+ *
141
+ * `subAgentClientId` is the client id from {@link ensureSubAgentIdentity};
142
+ * `undefined` in, and `undefined` out, since a sub-agent whose identity did not
143
+ * resolve has nothing to check.
144
+ */
145
+ export declare function resolveSubAgentSubject(subAgentClientId: string | undefined): UserSubjectRef | undefined;
146
+ /**
147
+ * The **spawn-level** sub-agent subject:
148
+ * `SubAgent:<clientId>|<session>|<type>[|<spawn>]`, the finest grain available.
149
+ *
150
+ * `perSpawnId` distinguishes two *concurrent* same-type sub-agents and is only
151
+ * present on harnesses that expose one (Cursor's `subagent_id`, Claude Code's
152
+ * `agent_id`, OpenClaw's `childRunId`, …). Where the harness exposes none the
153
+ * subject stops at the type and concurrent spawns share it — a limit of the
154
+ * harness, not of this model.
155
+ *
156
+ * As with the agent session subject, a missing session yields `undefined`
157
+ * rather than a placeholder.
158
+ */
159
+ export declare function resolveSubAgentSpawnSubject(client: OryAgentClient, args: {
160
+ subAgentClientId?: string;
161
+ subAgentType: string;
162
+ perSpawnId?: string;
163
+ sessionId?: string;
164
+ }): UserSubjectRef | undefined;