@ory/argus 0.14.0 → 1.0.1
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 -46
- 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 +39 -91
- package/assets/skills/ory-e2b-sandbox/SKILL.md +18 -17
- package/assets/skills/ory-temporal-worker/SKILL.md +31 -33
- package/assets/skills/permissions-onboarding/SKILL.md +131 -104
- package/dist/adapters.d.ts +88 -32
- package/dist/adapters.js +443 -164
- package/dist/agent-auth.d.ts +226 -67
- package/dist/agent-auth.js +951 -205
- package/dist/auth-store.d.ts +37 -2
- package/dist/auth-store.js +37 -3
- package/dist/auth.d.ts +33 -4
- package/dist/auth.js +163 -20
- package/dist/bash-parser.d.ts +98 -0
- package/dist/bash-parser.js +396 -0
- package/dist/branding.d.ts +77 -16
- package/dist/branding.js +93 -23
- 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 -239
- package/dist/client.d.ts +175 -138
- package/dist/client.js +672 -391
- package/dist/config.d.ts +217 -54
- package/dist/config.js +461 -42
- package/dist/context.d.ts +10 -0
- package/dist/context.js +21 -0
- package/dist/contract-suite.d.ts +3 -5
- package/dist/contract-suite.js +75 -47
- 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 +28 -23
- package/dist/index.js +173 -58
- 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.js +7 -7
- 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 +159 -36
- package/dist/local/ports.d.ts +158 -0
- package/dist/local/ports.js +443 -0
- package/dist/local/seed.d.ts +13 -5
- package/dist/local/seed.js +62 -38
- 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 +21 -37
- package/dist/runtime-credential.d.ts +140 -0
- package/dist/runtime-credential.js +572 -0
- package/dist/runtime.d.ts +413 -0
- package/dist/runtime.js +825 -0
- package/dist/setup.d.ts +23 -47
- package/dist/setup.js +59 -116
- package/dist/skills.js +0 -7
- package/dist/status-cli.d.ts +27 -11
- package/dist/status-cli.js +121 -115
- package/dist/status-data.d.ts +116 -17
- package/dist/status-data.js +121 -38
- 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 +71 -34
- package/dist/uninstall.js +217 -85
- package/dist/user-login.d.ts +9 -10
- package/dist/user-login.js +56 -75
- package/dist/watch-cli.d.ts +6 -0
- package/dist/watch-cli.js +217 -0
- package/package.json +4 -27
- package/assets/commands/dashboard.md +0 -34
- package/dist/dashboard-cli.d.ts +0 -8
- package/dist/dashboard-cli.js +0 -70
- package/dist/dev.d.ts +0 -103
- package/dist/dev.js +0 -583
- package/dist/interactive-setup.d.ts +0 -286
- package/dist/interactive-setup.js +0 -1734
- 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/project-api-key.d.ts +0 -69
- package/dist/project-api-key.js +0 -147
- package/dist/setup-actions.d.ts +0 -232
- package/dist/setup-actions.js +0 -507
- 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
- package/dist/web/api.d.ts +0 -33
- package/dist/web/api.js +0 -294
- package/dist/web/launch.d.ts +0 -11
- package/dist/web/launch.js +0 -96
- package/dist/web/server.d.ts +0 -20
- package/dist/web/server.js +0 -233
- package/dist/web/types.d.ts +0 -65
- package/dist/web/types.js +0 -2
- package/dist/webapp/assets/index-Wucl4SZs.css +0 -1
- package/dist/webapp/assets/index-m-GtEdq0.js +0 -49
- package/dist/webapp/favicon.ico +0 -0
- package/dist/webapp/index.html +0 -15
package/dist/subject.js
CHANGED
|
@@ -2,28 +2,45 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* User-subject resolution for permission checks.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Every principal — user, agent, sub-agent — is addressed as a **SubjectSet**
|
|
6
|
+
* `<namespace>:<object>` (e.g. `User:<UUID>`). This is the form the Ory Console's
|
|
7
|
+
* *Add relationship* dialog writes, the form the local-stack seed writes, and the
|
|
8
|
+
* Zanzibar-idiomatic composition point (grant `Role:developer` `use` on a tool and
|
|
9
|
+
* add principals to the role, rather than fanning out a tuple per subject). It is
|
|
10
|
+
* the only first-class subject form: Keto does no cross-form expansion, so a check
|
|
11
|
+
* must match the write shape exactly, and standardizing on one shape removes that
|
|
12
|
+
* silent-misfire footgun.
|
|
7
13
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `session:<id>`). Backward-compatible default when no subject
|
|
14
|
-
* namespace is configured.
|
|
15
|
-
*
|
|
16
|
-
* The selection is driven by the user-subject namespace: `ORY_USER_SUBJECT_NAMESPACE`
|
|
17
|
-
* takes precedence, then the client's configured `userSubjectNamespace` (persisted
|
|
18
|
-
* in the shared config, e.g. by the local-stack install path). When a namespace is
|
|
19
|
-
* present the resolver builds a SubjectSet using it; otherwise it falls back to the
|
|
20
|
-
* direct SubjectID chain.
|
|
14
|
+
* The namespace is resolved by precedence: `ORY_USER_SUBJECT_NAMESPACE` (env) →
|
|
15
|
+
* the client's configured `userSubjectNamespace` (persisted in the shared config)
|
|
16
|
+
* → the {@link DEFAULT_USER_SUBJECT_NAMESPACE} (`User`). The env/config values now
|
|
17
|
+
* *override* the default namespace; they are no longer an opt-in switch that
|
|
18
|
+
* toggles between SubjectSet and a legacy direct SubjectID.
|
|
21
19
|
*/
|
|
22
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.AGENT_SUBJECT_ID_ENV_VAR = exports.SUBJECT_AXIS_SEPARATOR = exports.SESSION_NAMESPACE = exports.SUBAGENT_NAMESPACE = exports.AGENT_NAMESPACE = exports.DEFAULT_USER_SUBJECT_NAMESPACE = void 0;
|
|
23
22
|
exports.runWithUserSubject = runWithUserSubject;
|
|
24
23
|
exports.resolveUserSubject = resolveUserSubject;
|
|
25
24
|
exports.subjectLabel = subjectLabel;
|
|
25
|
+
exports.validateSubjectOverride = validateSubjectOverride;
|
|
26
|
+
exports.readAgentSubjectOverride = readAgentSubjectOverride;
|
|
27
|
+
exports.resolveAgentSubject = resolveAgentSubject;
|
|
28
|
+
exports.resolveAgentSessionSubject = resolveAgentSessionSubject;
|
|
29
|
+
exports.resolveSubAgentSubject = resolveSubAgentSubject;
|
|
30
|
+
exports.resolveSubAgentSpawnSubject = resolveSubAgentSpawnSubject;
|
|
26
31
|
const node_async_hooks_1 = require("node:async_hooks");
|
|
32
|
+
/**
|
|
33
|
+
* Default namespace a resolved user principal is addressed under. The
|
|
34
|
+
* local-stack seed, the Console *Add relationship* dialog, and the Network OPL
|
|
35
|
+
* provisioning all use `User`, so it is the out-of-the-box default.
|
|
36
|
+
*/
|
|
37
|
+
exports.DEFAULT_USER_SUBJECT_NAMESPACE = "User";
|
|
38
|
+
/** Namespace the agent principal is addressed under (delegation subjects). */
|
|
39
|
+
exports.AGENT_NAMESPACE = "Agent";
|
|
40
|
+
/** Namespace a sub-agent principal is addressed under. */
|
|
41
|
+
exports.SUBAGENT_NAMESPACE = "SubAgent";
|
|
42
|
+
/** Namespace the no-user-identity `session:<id>` fallback is addressed under. */
|
|
43
|
+
exports.SESSION_NAMESPACE = "Session";
|
|
27
44
|
/**
|
|
28
45
|
* Per-async-context user-subject override. Populated by
|
|
29
46
|
* {@link runWithUserSubject} so multi-user server integrations (Vercel AI
|
|
@@ -37,7 +54,7 @@ const perCallUserSubject = new node_async_hooks_1.AsyncLocalStorage();
|
|
|
37
54
|
* client's user principal and the env overrides in
|
|
38
55
|
* {@link resolveUserSubject}. The override only applies within `fn`'s
|
|
39
56
|
* async context, so concurrent calls cannot observe each other's subject.
|
|
40
|
-
*
|
|
57
|
+
* SubjectSet shaping still applies.
|
|
41
58
|
*
|
|
42
59
|
* A missing/empty `subject` is a no-op: `fn` runs with the normal
|
|
43
60
|
* resolution chain.
|
|
@@ -47,6 +64,10 @@ function runWithUserSubject(subject, fn) {
|
|
|
47
64
|
return fn();
|
|
48
65
|
return perCallUserSubject.run(subject, fn);
|
|
49
66
|
}
|
|
67
|
+
/** Build a SubjectSet ref with an empty relation (a plain subject, not a subject-set expansion). */
|
|
68
|
+
function subjectSet(namespace, object) {
|
|
69
|
+
return { subjectSet: { namespace, object, relation: "" } };
|
|
70
|
+
}
|
|
50
71
|
/**
|
|
51
72
|
* Resolve the user subject for permission checks. Prefers a per-call
|
|
52
73
|
* override installed via {@link runWithUserSubject}, then the user
|
|
@@ -54,32 +75,196 @@ function runWithUserSubject(subject, fn) {
|
|
|
54
75
|
* legacy `ORY_AGENT_SUBJECT_ID`, then the caller-supplied `fallback`
|
|
55
76
|
* (typically `session:<id>`).
|
|
56
77
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
78
|
+
* Always returns a SubjectSet. A resolved principal is addressed under the
|
|
79
|
+
* configured user namespace (`ORY_USER_SUBJECT_NAMESPACE` → client config →
|
|
80
|
+
* {@link DEFAULT_USER_SUBJECT_NAMESPACE}). A `session:`-prefixed fallback is
|
|
81
|
+
* addressed under {@link SESSION_NAMESPACE} (`Session:<id>`). Only when nothing
|
|
82
|
+
* resolves at all do we emit the direct `agent:unknown` sentinel — an error
|
|
83
|
+
* marker that must never match a stored tuple.
|
|
60
84
|
*/
|
|
61
85
|
function resolveUserSubject(client, fallback) {
|
|
62
|
-
const
|
|
86
|
+
const principal = perCallUserSubject.getStore()
|
|
63
87
|
?? client.userPrincipal.subject
|
|
64
88
|
?? process.env.ORY_USER_SUBJECT_ID
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
// Validated, not read raw: the same value feeds the agent subject, so a
|
|
90
|
+
// value rejected there must not slip in here (#241).
|
|
91
|
+
?? readAgentSubjectOverride();
|
|
92
|
+
const ns = process.env.ORY_USER_SUBJECT_NAMESPACE?.trim()
|
|
93
|
+
|| client.userSubjectNamespace
|
|
94
|
+
|| exports.DEFAULT_USER_SUBJECT_NAMESPACE;
|
|
95
|
+
if (principal)
|
|
96
|
+
return subjectSet(ns, principal);
|
|
97
|
+
// No principal — use the caller's fallback. A `session:<id>` fallback is
|
|
98
|
+
// addressed under the Session namespace so it is a well-formed SubjectSet that
|
|
99
|
+
// could match a Session-scoped grant, rather than being wrapped under `User`.
|
|
100
|
+
if (fallback) {
|
|
101
|
+
const colon = fallback.indexOf(":");
|
|
102
|
+
if (colon > 0 && fallback.slice(0, colon).toLowerCase() === "session") {
|
|
103
|
+
return subjectSet(exports.SESSION_NAMESPACE, fallback.slice(colon + 1));
|
|
104
|
+
}
|
|
105
|
+
return subjectSet(ns, fallback);
|
|
72
106
|
}
|
|
73
|
-
if (subject)
|
|
74
|
-
return { subjectId: subject };
|
|
75
107
|
return { subjectId: "agent:unknown" };
|
|
76
108
|
}
|
|
77
109
|
/**
|
|
78
110
|
* Printable label for a `UserSubjectRef`. Used in denial messages and
|
|
79
|
-
*
|
|
111
|
+
* activity attributes. SubjectSets render as `<namespace>:<object>`.
|
|
80
112
|
*/
|
|
81
113
|
function subjectLabel(ref) {
|
|
82
114
|
return "subjectSet" in ref
|
|
83
115
|
? `${ref.subjectSet.namespace}:${ref.subjectSet.object}`
|
|
84
116
|
: ref.subjectId;
|
|
85
117
|
}
|
|
118
|
+
// ─── Machine principals (agent / sub-agent) ──────────────────────────
|
|
119
|
+
//
|
|
120
|
+
// The agent's OAuth2 credential is session-scoped: one DCR client per
|
|
121
|
+
// `(projectUrl, harness, session)`, plus one client per typed sub-agent in that
|
|
122
|
+
// session. Sessionless integrations use a reserved persistent slot.
|
|
123
|
+
//
|
|
124
|
+
// The registered client id is the principal root. Session and spawn axes remain
|
|
125
|
+
// available as narrower broker subjects and are appended with `|`:
|
|
126
|
+
//
|
|
127
|
+
// Agent:<clientId> session credential
|
|
128
|
+
// Agent:<clientId>|<session> one run
|
|
129
|
+
// SubAgent:<clientId> typed sub-agent credential
|
|
130
|
+
// SubAgent:<clientId>|<session>|<type>[|<spawn>] one spawn
|
|
131
|
+
//
|
|
132
|
+
// Every one of these is computable locally, so a block can be written in the
|
|
133
|
+
// Console against the exact string the plugin will check. Nothing here depends
|
|
134
|
+
// on the broker's node id, which is opaque and server-assigned.
|
|
135
|
+
//
|
|
136
|
+
// See docs/session-identity.md.
|
|
137
|
+
/** Separator between the credential client id and narrower scoping axes. */
|
|
138
|
+
exports.SUBJECT_AXIS_SEPARATOR = "|";
|
|
139
|
+
/**
|
|
140
|
+
* Join a client id with the derived axes that narrow it. Empty and whitespace-
|
|
141
|
+
* only axes are dropped rather than producing a trailing separator — a subject
|
|
142
|
+
* ending in `|` would silently never match a stored relation.
|
|
143
|
+
*/
|
|
144
|
+
function deriveSubject(clientId, ...axes) {
|
|
145
|
+
return [clientId, ...axes.map((a) => a?.trim()).filter((a) => !!a)].join(exports.SUBJECT_AXIS_SEPARATOR);
|
|
146
|
+
}
|
|
147
|
+
/** The env var that overrides the agent's subject. */
|
|
148
|
+
exports.AGENT_SUBJECT_ID_ENV_VAR = "ORY_AGENT_SUBJECT_ID";
|
|
149
|
+
/**
|
|
150
|
+
* Validate a subject override (issue #241).
|
|
151
|
+
*
|
|
152
|
+
* `deriveSubject` above already refuses to produce a trailing separator,
|
|
153
|
+
* because a subject ending in `|` silently never matches a stored relation.
|
|
154
|
+
* The same care is owed to the *base* it joins onto, which arrives from an
|
|
155
|
+
* operator-settable env var and was previously used verbatim.
|
|
156
|
+
*
|
|
157
|
+
* A value containing the axis separator is rejected for two independent
|
|
158
|
+
* reasons:
|
|
159
|
+
*
|
|
160
|
+
* 1. **It collapses the identity grains.** The credential, session, and spawn
|
|
161
|
+
* subjects are told apart only by separator position, so an override
|
|
162
|
+
* containing one makes them ambiguous: `override="a|b"` in session `c` and
|
|
163
|
+
* `override="a"` in session `b|c` produce the same string. A session-scoped
|
|
164
|
+
* block written to stop one runaway run could then match the base credential, or a
|
|
165
|
+
* different run — and all three grains are read together in one batched
|
|
166
|
+
* check, so this is a live semantics break rather than a theoretical one.
|
|
167
|
+
* 2. **It forges a delegation-node reference** (#225). A join key is exactly
|
|
168
|
+
* `<identity>|<harness>|<host>|<session>`, and the two string spaces stay
|
|
169
|
+
* disjoint today only because a Hydra client id is a UUID and contains no
|
|
170
|
+
* separator. This env var is the one supported knob that breaks that.
|
|
171
|
+
*
|
|
172
|
+
* Rejected rather than sanitized: silently stripping the separator would
|
|
173
|
+
* produce a *different* subject than the operator asked for, which fails just
|
|
174
|
+
* as confusingly and is harder to notice. Ignoring the value falls back to the
|
|
175
|
+
* client id — a real, working identity — and says so.
|
|
176
|
+
*/
|
|
177
|
+
function validateSubjectOverride(raw, envVar = exports.AGENT_SUBJECT_ID_ENV_VAR) {
|
|
178
|
+
if (raw === undefined)
|
|
179
|
+
return {};
|
|
180
|
+
const trimmed = raw.trim();
|
|
181
|
+
if (!trimmed) {
|
|
182
|
+
return {
|
|
183
|
+
warning: `${envVar} is set but empty — ignoring it and using the client id as the subject.`,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (trimmed.includes(exports.SUBJECT_AXIS_SEPARATOR)) {
|
|
187
|
+
return {
|
|
188
|
+
warning: `${envVar}="${trimmed}" contains "${exports.SUBJECT_AXIS_SEPARATOR}", which separates the derived `
|
|
189
|
+
+ `session and spawn axes of a subject — a value containing it is ambiguous with a `
|
|
190
|
+
+ `session-scoped subject and with a delegation-node reference. Ignoring it and using the `
|
|
191
|
+
+ `client id as the subject.`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return { value: trimmed };
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Read and validate `ORY_AGENT_SUBJECT_ID`. Returns `undefined` when unset or
|
|
198
|
+
* rejected, pushing an explanation onto `warnings` in the latter case.
|
|
199
|
+
*
|
|
200
|
+
* One reader for every site that consumes the override, so the rule cannot hold
|
|
201
|
+
* in the resolver and not in what `status` reports.
|
|
202
|
+
*/
|
|
203
|
+
function readAgentSubjectOverride(env = process.env, warnings) {
|
|
204
|
+
const { value, warning } = validateSubjectOverride(env[exports.AGENT_SUBJECT_ID_ENV_VAR]);
|
|
205
|
+
if (warning)
|
|
206
|
+
warnings?.push(warning);
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The registered agent credential subject: `Agent:<clientId>`. For DCR this
|
|
211
|
+
* client belongs to one session; static credentials may be shared explicitly.
|
|
212
|
+
*
|
|
213
|
+
* `undefined` when no agent principal is populated — the agent gate never
|
|
214
|
+
* blocks, so a session with no resolved machine identity is normal and callers
|
|
215
|
+
* must treat a missing agent subject as "nothing to check", not as a deny.
|
|
216
|
+
*/
|
|
217
|
+
function resolveAgentSubject(client) {
|
|
218
|
+
const subject = client.agentPrincipal.subject;
|
|
219
|
+
return subject ? subjectSet(exports.AGENT_NAMESPACE, subject) : undefined;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The **session-level** agent subject: `Agent:<clientId>|<session>`, unique to
|
|
223
|
+
* one run. Used for a block that stops the session's narrower subject without
|
|
224
|
+
* blocking its base credential subject.
|
|
225
|
+
*
|
|
226
|
+
* `undefined` when there is no agent principal, and — deliberately — also when
|
|
227
|
+
* there is no session. A caller with no session concept (an SDK integration in
|
|
228
|
+
* a long-running service) has exactly one sessionless credential subject;
|
|
229
|
+
* inventing a placeholder session would either collapse every run onto
|
|
230
|
+
* one string or produce a subject no admin could have written.
|
|
231
|
+
*/
|
|
232
|
+
function resolveAgentSessionSubject(client, sessionId) {
|
|
233
|
+
const subject = client.agentPrincipal.subject;
|
|
234
|
+
const session = (sessionId ?? client.sessionId)?.trim();
|
|
235
|
+
if (!subject || !session)
|
|
236
|
+
return undefined;
|
|
237
|
+
return subjectSet(exports.AGENT_NAMESPACE, deriveSubject(subject, session));
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* The registered sub-agent credential subject: `SubAgent:<clientId>` for one
|
|
241
|
+
* typed sub-agent in one session.
|
|
242
|
+
*
|
|
243
|
+
* `subAgentClientId` is the client id from {@link ensureSubAgentIdentity};
|
|
244
|
+
* `undefined` in, and `undefined` out, since a sub-agent whose identity did not
|
|
245
|
+
* resolve has nothing to check.
|
|
246
|
+
*/
|
|
247
|
+
function resolveSubAgentSubject(subAgentClientId) {
|
|
248
|
+
return subAgentClientId
|
|
249
|
+
? subjectSet(exports.SUBAGENT_NAMESPACE, subAgentClientId)
|
|
250
|
+
: undefined;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The **spawn-level** sub-agent subject:
|
|
254
|
+
* `SubAgent:<clientId>|<session>|<type>[|<spawn>]`, the finest grain available.
|
|
255
|
+
*
|
|
256
|
+
* `perSpawnId` distinguishes two *concurrent* same-type sub-agents and is only
|
|
257
|
+
* present on harnesses that expose one (Cursor's `subagent_id`, Claude Code's
|
|
258
|
+
* `agent_id`, OpenClaw's `childRunId`, …). Where the harness exposes none the
|
|
259
|
+
* subject stops at the type and concurrent spawns share it — a limit of the
|
|
260
|
+
* harness, not of this model.
|
|
261
|
+
*
|
|
262
|
+
* As with the agent session subject, a missing session yields `undefined`
|
|
263
|
+
* rather than a placeholder.
|
|
264
|
+
*/
|
|
265
|
+
function resolveSubAgentSpawnSubject(client, args) {
|
|
266
|
+
const session = (args.sessionId ?? client.sessionId)?.trim();
|
|
267
|
+
if (!args.subAgentClientId || !session)
|
|
268
|
+
return undefined;
|
|
269
|
+
return subjectSet(exports.SUBAGENT_NAMESPACE, deriveSubject(args.subAgentClientId, session, args.subAgentType, args.perSpawnId));
|
|
270
|
+
}
|
package/dist/testing.d.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { vi } from "vitest";
|
|
9
9
|
import { OryAgentClient } from "./client.js";
|
|
10
|
-
import type {
|
|
10
|
+
import type { ActivityEntry } from "./logger.js";
|
|
11
|
+
import type { PermissionMode } from "./config.js";
|
|
11
12
|
export { runHarnessContractSuite, type HarnessContractAdapter, type ContractContext, type ContractGates, type ContractOutcome, } from "./contract-suite.js";
|
|
12
13
|
/**
|
|
13
14
|
* Create an OryAgentClient with session caching disabled.
|
|
@@ -15,18 +16,26 @@ export { runHarnessContractSuite, type HarnessContractAdapter, type ContractCont
|
|
|
15
16
|
*/
|
|
16
17
|
export declare function createMockClient(overrides?: Partial<ConstructorParameters<typeof OryAgentClient>[0]>): OryAgentClient;
|
|
17
18
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* model a failed probe.
|
|
19
|
+
* Spy on {@link OryAgentClient.recordDelegation} (the agent-security broker
|
|
20
|
+
* call the delegation writers use). Resolves to `result` (a stub node by
|
|
21
|
+
* default). Returns the spy so callers assert the broker inputs or
|
|
22
|
+
* `.mockRejectedValue(...)` a broker failure.
|
|
23
23
|
*/
|
|
24
|
-
export declare function
|
|
24
|
+
export declare function spyRecordDelegation(client: OryAgentClient, result?: {
|
|
25
|
+
nodeId: string;
|
|
26
|
+
delegationChain: string[];
|
|
27
|
+
}): import("vitest").Mock<(input: import("./client.js").RecordDelegationInput, options?: {
|
|
28
|
+
fetchImpl?: typeof fetch;
|
|
29
|
+
activityAttributes?: Record<string, unknown>;
|
|
30
|
+
agentToken?: string;
|
|
31
|
+
runtimeCredential?: import("./runtime-credential.js").RuntimeCredential;
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
}) => Promise<import("./client.js").RecordDelegationResult>>;
|
|
25
34
|
/**
|
|
26
35
|
* Stub an internal API instance method on the client.
|
|
27
36
|
* Returns a vi.fn mock so callers can assert on calls.
|
|
28
37
|
*/
|
|
29
|
-
export declare function stubApi<K extends "frontend" | "oauth2" | "permission"
|
|
38
|
+
export declare function stubApi<K extends "frontend" | "oauth2" | "permission">(client: OryAgentClient, api: K, method: string, impl: (...args: unknown[]) => unknown): ReturnType<typeof vi.fn>;
|
|
30
39
|
/** Successful session verification response (wraps Ory API shape). */
|
|
31
40
|
export declare const MOCK_SESSION_RESPONSE: {
|
|
32
41
|
data: {
|
|
@@ -132,28 +141,40 @@ export declare function makeMfaRequiredError(): Record<string, unknown>;
|
|
|
132
141
|
/** Axios 401 with session_inactive error id. */
|
|
133
142
|
export declare function makeSessionInactiveError(): Record<string, unknown>;
|
|
134
143
|
/**
|
|
135
|
-
* Get all recorded
|
|
144
|
+
* Get all recorded activity events from a client, optionally filtered by event.
|
|
136
145
|
*/
|
|
137
|
-
export declare function
|
|
146
|
+
export declare function getActivityEvents(client: OryAgentClient, event?: string): ActivityEntry[];
|
|
138
147
|
/**
|
|
139
|
-
* Assert the
|
|
140
|
-
* Checks the `toolName` attribute on each span.
|
|
148
|
+
* Assert the activity log recorded the expected tool names for an event type.
|
|
141
149
|
*/
|
|
142
|
-
export declare function
|
|
150
|
+
export declare function expectActivityTools(client: OryAgentClient, event: string, expectedTools: string[]): void;
|
|
143
151
|
/**
|
|
144
152
|
* Set standard Ory env vars for a configured + authenticated test.
|
|
145
153
|
* Returns a cleanup function that restores the previous state.
|
|
146
154
|
*/
|
|
147
155
|
export declare function setOryEnv(overrides?: Partial<{
|
|
148
156
|
projectUrl: string;
|
|
149
|
-
|
|
150
|
-
|
|
157
|
+
agentSecurityUrl: string;
|
|
158
|
+
/**
|
|
159
|
+
* Public OAuth2 client id. Together with `projectUrl` this is what makes
|
|
160
|
+
* Agent Security connected — pass `undefined` for either to exercise the
|
|
161
|
+
* not-connected path.
|
|
162
|
+
*/
|
|
163
|
+
oauth2ClientId: string;
|
|
164
|
+
/**
|
|
165
|
+
* Pre-supplied user OAuth2 access token (`ORY_USER_OAUTH2_TOKEN`) — the one
|
|
166
|
+
* accepted env credential. Pass `undefined` so the login gate does not
|
|
167
|
+
* short-circuit on it.
|
|
168
|
+
*/
|
|
169
|
+
userToken: string;
|
|
151
170
|
subjectId: string;
|
|
152
171
|
namespace: string;
|
|
153
172
|
/**
|
|
154
173
|
* Permission mode for the test. Defaults to `"enforce"` so existing
|
|
155
|
-
* test assertions that exercise the deny path still see a block.
|
|
156
|
-
*
|
|
174
|
+
* test assertions that exercise the deny path still see a block. The mode
|
|
175
|
+
* is now a server-read Keto permission, so this sets the value the
|
|
176
|
+
* {@link stubPermissionAllowed}/`Denied` stubs report for the mode check —
|
|
177
|
+
* it is NOT an env var (there is no `ORY_PERMISSION_MODE` anymore).
|
|
157
178
|
*/
|
|
158
179
|
permissionMode: "observe" | "enforce";
|
|
159
180
|
}>): () => void;
|
|
@@ -164,9 +185,37 @@ export declare function setOryEnv(overrides?: Partial<{
|
|
|
164
185
|
* that restores the env var and removes the temp directory.
|
|
165
186
|
*
|
|
166
187
|
* Use `saveConfig(...)` from `./config.js` inside the test to shape the
|
|
167
|
-
* isolated config (e.g. `saveConfig({
|
|
188
|
+
* isolated config (e.g. `saveConfig({ projectUrl, oauth2ClientId })`).
|
|
168
189
|
*/
|
|
169
190
|
export declare function useTempConfigDir(): () => void;
|
|
191
|
+
/** Placeholder values `connectSecurity` / `disconnectSecurity` write. */
|
|
192
|
+
export declare const TEST_PROJECT_URL = "https://test.projects.oryapis.com";
|
|
193
|
+
export declare const TEST_AGENT_SECURITY_URL = "https://agents.console.ory.com";
|
|
194
|
+
export declare const TEST_OAUTH2_CLIENT_ID = "test-login-client";
|
|
195
|
+
/**
|
|
196
|
+
* Put the isolated config into the **connected** state so the auth gates and
|
|
197
|
+
* permission checks actually run.
|
|
198
|
+
*
|
|
199
|
+
* Agent Security is connected exactly when a project URL and an OAuth2 client
|
|
200
|
+
* id both resolve — there is no flag to set. Tests that exercise checks need
|
|
201
|
+
* both, and writing them through one named call keeps that requirement in one
|
|
202
|
+
* place instead of two literals in every `beforeEach`.
|
|
203
|
+
*
|
|
204
|
+
* Call after `useTempConfigDir()` so it writes to the temp config.
|
|
205
|
+
*/
|
|
206
|
+
export declare function connectSecurity(overrides?: {
|
|
207
|
+
projectUrl?: string;
|
|
208
|
+
agentSecurityUrl?: string;
|
|
209
|
+
oauth2ClientId?: string;
|
|
210
|
+
}): void;
|
|
211
|
+
/**
|
|
212
|
+
* Put the isolated config into a partial connection state for contract tests.
|
|
213
|
+
*
|
|
214
|
+
* `which` selects which connection values are absent. The `no-client-id` case
|
|
215
|
+
* now exercises the reserved client default rather than a disconnected state.
|
|
216
|
+
* The `neither` and `no-project-url` forms remain disconnected.
|
|
217
|
+
*/
|
|
218
|
+
export declare function disconnectSecurity(which?: "neither" | "no-client-id" | "no-project-url"): void;
|
|
170
219
|
/**
|
|
171
220
|
* Clear all Ory env vars (simulate unconfigured state).
|
|
172
221
|
*/
|
|
@@ -185,33 +234,20 @@ export declare function stubSessionExpired(client: OryAgentClient): import("vite
|
|
|
185
234
|
export declare function stubOAuth2Success(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
186
235
|
/** Stub introspectToken to return inactive. */
|
|
187
236
|
export declare function stubOAuth2Inactive(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
237
|
+
/**
|
|
238
|
+
* Set the mode the server-read stub reports. Also clears the in-memory
|
|
239
|
+
* permission-mode cache so the new value takes effect immediately (tests run
|
|
240
|
+
* with `ORY_PERMISSION_MODE_TTL_MS=0`, but resetting is belt-and-suspenders).
|
|
241
|
+
*/
|
|
242
|
+
export declare function setTestPermissionMode(mode: PermissionMode): void;
|
|
188
243
|
/** Stub checkPermission to allow. */
|
|
189
244
|
export declare function stubPermissionAllowed(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
190
|
-
/** Stub checkPermission to deny. */
|
|
245
|
+
/** Stub checkPermission to deny (the tool check; the mode check still answers `currentTestMode`). */
|
|
191
246
|
export declare function stubPermissionDenied(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
192
247
|
/** Stub checkPermission to throw a network error. */
|
|
193
248
|
export declare function stubPermissionNetworkError(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
194
249
|
/** Stub checkPermission to throw a rate-limit error. */
|
|
195
250
|
export declare function stubPermissionRateLimited(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
196
|
-
/**
|
|
197
|
-
* Spy on the public `createRelationship` method and resolve successfully.
|
|
198
|
-
* Prefer this over stubbing the internal relationship API: `setAgentPrincipal`
|
|
199
|
-
* rebuilds the API instances when the agent token changes (e.g. inside
|
|
200
|
-
* `sessionStart`), which silently discards API-level stubs.
|
|
201
|
-
*/
|
|
202
|
-
export declare function spyRelationshipCreated(client: OryAgentClient): import("vitest").Mock<(check: import("./types.js").PermissionCheck, options?: {
|
|
203
|
-
spanAttributes?: Record<string, unknown>;
|
|
204
|
-
}) => Promise<{
|
|
205
|
-
created: boolean;
|
|
206
|
-
alreadyExisted: boolean;
|
|
207
|
-
}>>;
|
|
208
|
-
/** Spy on the public `createRelationship` method and reject. */
|
|
209
|
-
export declare function spyRelationshipCreateError(client: OryAgentClient): import("vitest").Mock<(check: import("./types.js").PermissionCheck, options?: {
|
|
210
|
-
spanAttributes?: Record<string, unknown>;
|
|
211
|
-
}) => Promise<{
|
|
212
|
-
created: boolean;
|
|
213
|
-
alreadyExisted: boolean;
|
|
214
|
-
}>>;
|
|
215
251
|
/** Stub both checkPermission (server allow) and batchCheckPermission (both allow). */
|
|
216
252
|
export declare function stubMcpAllowed(client: OryAgentClient): import("vitest").Mock<import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Procedure | import(".pnpm/@vitest+spy@4.1.7/node_modules/@vitest/spy", { with: { "resolution-mode": "import" } }).Constructable>;
|
|
217
253
|
/** Stub checkPermission to deny (server-only MCP check). */
|