@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.
- package/README.md +31 -45
- package/assets/commands/temporal-up.md +1 -1
- package/assets/skills/auth-setup/SKILL.md +1 -1
- package/assets/skills/local-dev/SKILL.md +17 -7
- package/assets/skills/ory-build-agent/SKILL.md +43 -97
- package/assets/skills/ory-e2b-sandbox/SKILL.md +18 -18
- package/assets/skills/ory-temporal-worker/SKILL.md +38 -42
- package/assets/skills/permissions-onboarding/SKILL.md +131 -104
- package/dist/adapters.d.ts +93 -30
- package/dist/adapters.js +464 -136
- package/dist/agent-auth.d.ts +258 -68
- package/dist/agent-auth.js +998 -202
- package/dist/auth-store.d.ts +37 -2
- package/dist/auth-store.js +37 -3
- package/dist/auth.d.ts +40 -4
- package/dist/auth.js +247 -19
- package/dist/bash-parser.d.ts +98 -0
- package/dist/bash-parser.js +396 -0
- package/dist/branding.d.ts +128 -0
- package/dist/branding.js +151 -0
- package/dist/build-info.json +4 -4
- package/dist/cli-invocation.d.ts +1 -1
- package/dist/cli-invocation.js +2 -1
- package/dist/cli.d.ts +20 -29
- package/dist/cli.js +271 -278
- package/dist/client.d.ts +175 -138
- package/dist/client.js +672 -391
- package/dist/config.d.ts +249 -57
- package/dist/config.js +486 -62
- package/dist/context.d.ts +10 -0
- package/dist/context.js +21 -0
- package/dist/contract-suite.d.ts +8 -8
- package/dist/contract-suite.js +88 -69
- package/dist/denial.d.ts +36 -3
- package/dist/denial.js +79 -10
- package/dist/event-reporter.d.ts +77 -0
- package/dist/event-reporter.js +776 -0
- package/dist/external-registrations-main.d.ts +10 -0
- package/dist/external-registrations-main.js +38 -0
- package/dist/external-registrations.d.ts +79 -0
- package/dist/external-registrations.js +188 -0
- package/dist/help-cli.d.ts +39 -0
- package/dist/help-cli.js +55 -0
- package/dist/hook-timeout.d.ts +64 -0
- package/dist/hook-timeout.js +88 -0
- package/dist/index.d.ts +31 -19
- package/dist/index.js +182 -31
- package/dist/lifecycle.d.ts +3 -3
- package/dist/lifecycle.js +38 -6
- package/dist/local/cli.js +11 -6
- package/dist/local/configs.d.ts +74 -18
- package/dist/local/configs.js +291 -84
- package/dist/local/health.d.ts +14 -0
- package/dist/local/health.js +50 -4
- package/dist/local/index.d.ts +2 -2
- package/dist/local/index.js +24 -10
- package/dist/local/manager.d.ts +20 -1
- package/dist/local/manager.js +160 -39
- package/dist/local/ports.d.ts +158 -0
- package/dist/local/ports.js +443 -0
- package/dist/local/seed.d.ts +22 -25
- package/dist/local/seed.js +88 -56
- package/dist/logger.d.ts +54 -25
- package/dist/logger.js +329 -63
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +10 -5
- package/dist/mirror-bootstrap.d.ts +48 -0
- package/dist/mirror-bootstrap.js +254 -0
- package/dist/opl.d.ts +289 -0
- package/dist/opl.js +446 -0
- package/dist/permission-mode.d.ts +87 -0
- package/dist/permission-mode.js +307 -0
- package/dist/permissions-cli.d.ts +13 -49
- package/dist/permissions-cli.js +154 -348
- package/dist/permissions.d.ts +148 -38
- package/dist/permissions.js +591 -45
- package/dist/post-install.d.ts +33 -0
- package/dist/post-install.js +127 -0
- package/dist/read-credential.d.ts +65 -0
- package/dist/read-credential.js +86 -0
- package/dist/registry/cli.js +5 -2
- package/dist/registry/config.d.ts +0 -17
- package/dist/registry/config.js +0 -23
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.js +2 -2
- package/dist/registry/manager.d.ts +4 -21
- package/dist/registry/manager.js +83 -55
- package/dist/runtime-credential.d.ts +140 -0
- package/dist/runtime-credential.js +572 -0
- package/dist/runtime.d.ts +408 -0
- package/dist/runtime.js +748 -0
- package/dist/setup.d.ts +23 -28
- package/dist/setup.js +57 -84
- package/dist/status-cli.d.ts +29 -13
- package/dist/status-cli.js +124 -144
- package/dist/status-data.d.ts +195 -0
- package/dist/status-data.js +333 -0
- package/dist/status-system.d.ts +24 -0
- package/dist/status-system.js +56 -0
- package/dist/subject.d.ts +126 -20
- package/dist/subject.js +215 -30
- package/dist/testing.d.ts +74 -38
- package/dist/testing.js +185 -68
- package/dist/tool-catalog.d.ts +53 -11
- package/dist/tool-catalog.js +164 -13
- package/dist/tool-metadata.d.ts +7 -6
- package/dist/tool-metadata.js +6 -5
- package/dist/types.d.ts +11 -1
- package/dist/uninstall.d.ts +74 -19
- package/dist/uninstall.js +224 -49
- package/dist/user-login.d.ts +22 -16
- package/dist/user-login.js +67 -96
- package/dist/watch-cli.d.ts +6 -0
- package/dist/watch-cli.js +217 -0
- package/package.json +3 -11
- package/dist/dev.d.ts +0 -103
- package/dist/dev.js +0 -584
- package/dist/interactive-setup.d.ts +0 -165
- package/dist/interactive-setup.js +0 -1546
- package/dist/local/jaeger-main.d.ts +0 -13
- package/dist/local/jaeger-main.js +0 -85
- package/dist/local/jaeger.d.ts +0 -50
- package/dist/local/jaeger.js +0 -162
- package/dist/otel/exporter.d.ts +0 -17
- package/dist/otel/exporter.js +0 -12
- package/dist/otel/index.d.ts +0 -2
- package/dist/otel/index.js +0 -8
- package/dist/otel/otlp.d.ts +0 -103
- package/dist/otel/otlp.js +0 -385
- package/dist/tracer.d.ts +0 -190
- package/dist/tracer.js +0 -481
- package/dist/watch-sandbox.d.ts +0 -9
- 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
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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
|
-
*
|
|
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;
|