@ory/argus 0.14.0 → 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 (150) hide show
  1. package/README.md +31 -46
  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 +39 -91
  6. package/assets/skills/ory-e2b-sandbox/SKILL.md +18 -17
  7. package/assets/skills/ory-temporal-worker/SKILL.md +31 -33
  8. package/assets/skills/permissions-onboarding/SKILL.md +131 -104
  9. package/dist/adapters.d.ts +88 -32
  10. package/dist/adapters.js +443 -164
  11. package/dist/agent-auth.d.ts +226 -67
  12. package/dist/agent-auth.js +951 -205
  13. package/dist/auth-store.d.ts +37 -2
  14. package/dist/auth-store.js +37 -3
  15. package/dist/auth.d.ts +33 -4
  16. package/dist/auth.js +163 -20
  17. package/dist/bash-parser.d.ts +98 -0
  18. package/dist/bash-parser.js +396 -0
  19. package/dist/branding.d.ts +77 -16
  20. package/dist/branding.js +93 -23
  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 -239
  26. package/dist/client.d.ts +175 -138
  27. package/dist/client.js +672 -391
  28. package/dist/config.d.ts +217 -54
  29. package/dist/config.js +461 -42
  30. package/dist/context.d.ts +10 -0
  31. package/dist/context.js +21 -0
  32. package/dist/contract-suite.d.ts +3 -5
  33. package/dist/contract-suite.js +75 -47
  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 +28 -23
  47. package/dist/index.js +173 -58
  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.js +7 -7
  54. package/dist/local/index.d.ts +2 -2
  55. package/dist/local/index.js +24 -10
  56. package/dist/local/manager.d.ts +20 -1
  57. package/dist/local/manager.js +159 -36
  58. package/dist/local/ports.d.ts +158 -0
  59. package/dist/local/ports.js +443 -0
  60. package/dist/local/seed.d.ts +13 -5
  61. package/dist/local/seed.js +62 -38
  62. package/dist/logger.d.ts +54 -25
  63. package/dist/logger.js +329 -63
  64. package/dist/mcp.d.ts +2 -2
  65. package/dist/mcp.js +10 -5
  66. package/dist/mirror-bootstrap.d.ts +48 -0
  67. package/dist/mirror-bootstrap.js +254 -0
  68. package/dist/opl.d.ts +289 -0
  69. package/dist/opl.js +446 -0
  70. package/dist/permission-mode.d.ts +87 -0
  71. package/dist/permission-mode.js +307 -0
  72. package/dist/permissions-cli.d.ts +13 -49
  73. package/dist/permissions-cli.js +154 -348
  74. package/dist/permissions.d.ts +148 -38
  75. package/dist/permissions.js +591 -45
  76. package/dist/post-install.d.ts +33 -0
  77. package/dist/post-install.js +127 -0
  78. package/dist/read-credential.d.ts +65 -0
  79. package/dist/read-credential.js +86 -0
  80. package/dist/registry/cli.js +5 -2
  81. package/dist/registry/config.d.ts +0 -17
  82. package/dist/registry/config.js +0 -23
  83. package/dist/registry/index.d.ts +1 -1
  84. package/dist/registry/index.js +2 -2
  85. package/dist/registry/manager.d.ts +4 -21
  86. package/dist/registry/manager.js +21 -37
  87. package/dist/runtime-credential.d.ts +140 -0
  88. package/dist/runtime-credential.js +572 -0
  89. package/dist/runtime.d.ts +408 -0
  90. package/dist/runtime.js +748 -0
  91. package/dist/setup.d.ts +23 -47
  92. package/dist/setup.js +59 -116
  93. package/dist/skills.js +0 -7
  94. package/dist/status-cli.d.ts +27 -11
  95. package/dist/status-cli.js +121 -115
  96. package/dist/status-data.d.ts +116 -17
  97. package/dist/status-data.js +121 -38
  98. package/dist/subject.d.ts +126 -20
  99. package/dist/subject.js +215 -30
  100. package/dist/testing.d.ts +74 -38
  101. package/dist/testing.js +185 -68
  102. package/dist/tool-catalog.d.ts +53 -11
  103. package/dist/tool-catalog.js +164 -13
  104. package/dist/tool-metadata.d.ts +7 -6
  105. package/dist/tool-metadata.js +6 -5
  106. package/dist/types.d.ts +11 -1
  107. package/dist/uninstall.d.ts +71 -34
  108. package/dist/uninstall.js +217 -85
  109. package/dist/user-login.d.ts +9 -10
  110. package/dist/user-login.js +56 -75
  111. package/dist/watch-cli.d.ts +6 -0
  112. package/dist/watch-cli.js +217 -0
  113. package/package.json +4 -27
  114. package/assets/commands/dashboard.md +0 -34
  115. package/dist/dashboard-cli.d.ts +0 -8
  116. package/dist/dashboard-cli.js +0 -70
  117. package/dist/dev.d.ts +0 -103
  118. package/dist/dev.js +0 -583
  119. package/dist/interactive-setup.d.ts +0 -286
  120. package/dist/interactive-setup.js +0 -1734
  121. package/dist/local/jaeger-main.d.ts +0 -13
  122. package/dist/local/jaeger-main.js +0 -85
  123. package/dist/local/jaeger.d.ts +0 -50
  124. package/dist/local/jaeger.js +0 -162
  125. package/dist/otel/exporter.d.ts +0 -17
  126. package/dist/otel/exporter.js +0 -12
  127. package/dist/otel/index.d.ts +0 -2
  128. package/dist/otel/index.js +0 -8
  129. package/dist/otel/otlp.d.ts +0 -103
  130. package/dist/otel/otlp.js +0 -385
  131. package/dist/project-api-key.d.ts +0 -69
  132. package/dist/project-api-key.js +0 -147
  133. package/dist/setup-actions.d.ts +0 -232
  134. package/dist/setup-actions.js +0 -507
  135. package/dist/tracer.d.ts +0 -190
  136. package/dist/tracer.js +0 -481
  137. package/dist/watch-sandbox.d.ts +0 -9
  138. package/dist/watch-sandbox.js +0 -81
  139. package/dist/web/api.d.ts +0 -33
  140. package/dist/web/api.js +0 -294
  141. package/dist/web/launch.d.ts +0 -11
  142. package/dist/web/launch.js +0 -96
  143. package/dist/web/server.d.ts +0 -20
  144. package/dist/web/server.js +0 -233
  145. package/dist/web/types.d.ts +0 -65
  146. package/dist/web/types.js +0 -2
  147. package/dist/webapp/assets/index-Wucl4SZs.css +0 -1
  148. package/dist/webapp/assets/index-m-GtEdq0.js +0 -49
  149. package/dist/webapp/favicon.ico +0 -0
  150. package/dist/webapp/index.html +0 -15
package/dist/opl.js ADDED
@@ -0,0 +1,446 @@
1
+ "use strict";
2
+ /**
3
+ * Ory Permission Language (OPL) schema for the Agent Security permission model.
4
+ *
5
+ * Argus relies on more than relation-tuple *existence*: a subject may be granted
6
+ * a tool and separately **blocked** from it, and the block must win. Keto
7
+ * permission is otherwise purely existence-based (no path ⇒ no access, any path
8
+ * ⇒ access), so the deny-override is modeled natively in OPL as a permit:
9
+ *
10
+ * use = users.includes(subject) && !blockedSubjects.includes(subject)
11
+ *
12
+ * Keto evaluates the permit in a single check, so a `blockedSubjects` relation
13
+ * beats a `users` grant no matter how the grant was reached (direct subject or
14
+ * subject-set expansion). Argus always checks the **`use` permit** (see
15
+ * `resolveCheckRelation`), so this model must be applied to the project for
16
+ * checks to resolve; with nothing blocked, `use` resolves identically to a bare
17
+ * `users` existence check, so grants alone allow.
18
+ *
19
+ * ## Naming conventions
20
+ *
21
+ * Every name here follows Ory Permissions' documented conventions
22
+ * (https://www.ory.com/docs/keto/concepts/namespaces#naming-conventions):
23
+ *
24
+ * - **Namespaces** are TypeScript classes named after the **singular** form of
25
+ * the type they describe, in UpperCamelCase — `AgentTool`, `ShellTool`,
26
+ * `Delegation`, `PermissionMode`, `User`, `Agent`, `SubAgent`, `Session`.
27
+ * - **Relations** are **plural** nouns naming the set of subjects on the far end
28
+ * of the edge, so each reads as an English sentence — `User:bob is in users of
29
+ * AgentTool:Bash`, `Agent:abc is in credentials of Delegation:agent:<join>`.
30
+ * - **Permits** are verbs, like Keto's own `view` — hence `use` for "may this
31
+ * subject use this tool", which is the question the gate actually asks.
32
+ * - A namespace holds a **homogeneous** type of object. That is why delegation
33
+ * nodes and the posture sentinels are *not* tool objects: `AgentTool` holds
34
+ * only tools, `Delegation` only delegation nodes, `PermissionMode` only the
35
+ * posture anchors. A single namespace holding all three is the case the
36
+ * convention doc calls out as incorrect, and it forced one class to carry a
37
+ * grab bag of unrelated relations.
38
+ *
39
+ * A relation and a permit cannot share a name in OPL — which is exactly why the
40
+ * grant relation is the plural `users` and the permit the singular verb `use`.
41
+ */
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.NODE_REFERENCE_NAMESPACES = exports.PRINCIPAL_NAMESPACES = exports.OPL_IMPORT_SYMBOLS = exports.OPL_IMPORT_MODULE = exports.PERMIT_USE = exports.PROJECT_POSTURE_OBJECT = exports.PERMISSION_MODE_OBJECT = exports.PERMISSION_MODE_NAMESPACE = exports.RELATION_OPTED_OUT_SUBJECTS = exports.AUTO_ENROLL_OBJECT = exports.ENROLLMENT_NAMESPACE = exports.RELATION_MEMBERS = exports.SUBAGENT_TYPE_NAMESPACE = exports.AGENT_TYPE_NAMESPACE = exports.AGENT_ROLE_NAMESPACE = exports.SUBAGENT_NODE_NAMESPACE = exports.AGENT_NODE_NAMESPACE = exports.DELEGATION_NAMESPACE = exports.DEFAULT_SHELL_NAMESPACE = exports.DEFAULT_NAMESPACE = exports.RELATION_ENFORCED_SUBJECTS = exports.RELATION_CREDENTIALS = exports.RELATION_BLOCKED_SUBJECTS = exports.RELATION_DELEGATES = exports.RELATION_USERS = void 0;
44
+ exports.renderOplImport = renderOplImport;
45
+ exports.renderPrincipalClass = renderPrincipalClass;
46
+ exports.renderToolNamespaceClass = renderToolNamespaceClass;
47
+ exports.renderDelegationNamespaceClass = renderDelegationNamespaceClass;
48
+ exports.renderAgentRoleNamespaceClass = renderAgentRoleNamespaceClass;
49
+ exports.renderAgentTypeNamespaceClass = renderAgentTypeNamespaceClass;
50
+ exports.renderEnrollmentNamespaceClass = renderEnrollmentNamespaceClass;
51
+ exports.renderPermissionModeNamespaceClass = renderPermissionModeNamespaceClass;
52
+ exports.buildAgentSecurityOpl = buildAgentSecurityOpl;
53
+ const subject_js_1 = require("./subject.js");
54
+ /**
55
+ * The relation that grants a subject use of a tool object — the set of subjects
56
+ * that may use it. Written in the Ory Console (Agent Security); the plugin only
57
+ * ever reads it. Reads as `User:bob is in users of AgentTool:Bash`.
58
+ */
59
+ exports.RELATION_USERS = "users";
60
+ /**
61
+ * The relation that records a delegation edge on a {@link DELEGATION_NAMESPACE}
62
+ * node (`user→agent`, `agent→subagent`) — the set of subjects that delegated to
63
+ * it. Written by the agent-security broker, never by the runtime; declared here
64
+ * so the local Keto mount (which loads this OPL) accepts the tuples.
65
+ */
66
+ exports.RELATION_DELEGATES = "delegates";
67
+ /**
68
+ * The relation that explicitly blocks subjects from a tool object
69
+ * (deny-override). A participle has no natural plural, so the relation names the
70
+ * set it holds: `User:bob is in blockedSubjects of AgentTool:curl`.
71
+ */
72
+ exports.RELATION_BLOCKED_SUBJECTS = "blockedSubjects";
73
+ /**
74
+ * The relation recording which OAuth2 clients a delegation node ran as:
75
+ * `Delegation:agent:<join>#credentials@(Agent:<clientId>)`. Written by the
76
+ * broker, never by the runtime, and never checked — a delegation node is keyed
77
+ * on (identity, harness, host) so that it survives credential rotation, which
78
+ * means the node alone cannot say which credential acted and two clients for one
79
+ * triple collapse onto it. This edge carries that missing axis, for audit and so
80
+ * a reader can join a node to its client exactly rather than by inference.
81
+ */
82
+ exports.RELATION_CREDENTIALS = "credentials";
83
+ /**
84
+ * The relation holding the subjects that are in **enforce** mode. Read (never
85
+ * written by the runtime) via a `checkPermission` on the
86
+ * {@link PERMISSION_MODE_OBJECT} anchor: an admin grants
87
+ * `PermissionMode:mode#enforcedSubjects@<subject>` (directly, or to a
88
+ * subject-set / group for a project-wide flip) to promote that principal from
89
+ * the default `observe` posture to `enforce`. Absence ⇒ `observe`. Named for the
90
+ * set it holds, since `enforce` is a verb and has no plural noun form.
91
+ */
92
+ exports.RELATION_ENFORCED_SUBJECTS = "enforcedSubjects";
93
+ /**
94
+ * Default permission namespace / OPL class name for harness tool objects
95
+ * (`AgentTool:Bash`). Singular, because the namespace describes *one tool*, and
96
+ * an object in it is one. Mirrors `ORY_PERMISSION_NAMESPACE`.
97
+ */
98
+ exports.DEFAULT_NAMESPACE = "AgentTool";
99
+ /**
100
+ * Default Keto namespace for decomposed shell-command sub-tools (issue #76) —
101
+ * one object per program/builtin word (`ShellTool:curl`). Rendered with the same
102
+ * grant/block model as {@link DEFAULT_NAMESPACE}, so an explicit
103
+ * `ShellTool:curl#blockedSubjects` overrides a `users` grant. Mirrors
104
+ * `resolveShellCommandNamespace()` in tool-catalog.ts.
105
+ */
106
+ exports.DEFAULT_SHELL_NAMESPACE = "ShellTool";
107
+ /**
108
+ * Namespace for delegation nodes — the current-state graph of who delegated to
109
+ * whom (`Delegation:agent:<join>`, `Delegation:subagent:<join>|<type>`). Its own
110
+ * namespace because a delegation node is not a tool: mixing the two put
111
+ * heterogeneous objects in one namespace and made the tool class carry
112
+ * delegation relations that no tool object ever uses. The broker owns both the
113
+ * objects and the tuples; the plugin treats a node id as opaque.
114
+ */
115
+ exports.DELEGATION_NAMESPACE = "Delegation";
116
+ /**
117
+ * Namespaces for **references to** delegation nodes — the parent pointer on a
118
+ * delegation edge (issue #225).
119
+ *
120
+ * `Agent` and `SubAgent` name a *principal identity*: an OAuth2 client that
121
+ * authenticates, and the subject an admin grants or blocks. A delegation edge's
122
+ * `delegates` subject is a different kind of thing — a pointer at another node
123
+ * in the graph, keyed by `<identity>|<harness>|<host>|<session>`. Both were
124
+ * addressed under `Agent` / `SubAgent`, so one namespace carried two meanings:
125
+ *
126
+ * Agent:<clientId> a principal identity
127
+ * Agent:<identity>|<harness>|<host>|<session> a graph-node reference
128
+ *
129
+ * Nothing written today is ambiguous, because a Hydra client id is a UUID and a
130
+ * join key always contains separators — but that disjointness is a property of
131
+ * the *data*, not of the model, and it is reachable-breakable through
132
+ * `ORY_AGENT_SUBJECT_ID` (guarded separately in #241). The costlier problem is
133
+ * silent: an admin, or a Console UI, that reads a node id off the delegation
134
+ * graph and writes it as a grant subject produces a tuple that is accepted,
135
+ * stored, and never matches any check — the same failure the SubjectSet-vs-
136
+ * SubjectID invariant exists to prevent, one level up. That surface is four
137
+ * relations wide (`users`, `blockedSubjects`, `members`, `enforcedSubjects`).
138
+ *
139
+ * Splitting the two meanings apart makes the wrong-form grant name a namespace
140
+ * that has no `users` relation at all, rather than looking plausible. It also
141
+ * lets the broker's downward walk use the right namespace per level instead of
142
+ * guessing `Agent` for every level — the latent inconsistency that would become
143
+ * a real bug the moment nested sub-agents ship.
144
+ */
145
+ exports.AGENT_NODE_NAMESPACE = "AgentNode";
146
+ /** Sub-agent counterpart of {@link AGENT_NODE_NAMESPACE}. */
147
+ exports.SUBAGENT_NODE_NAMESPACE = "SubAgentNode";
148
+ /**
149
+ * Namespace for the shared role a project grants tools to, rather than granting
150
+ * every identity directly (`AgentRole:agent-security-users#members@(User:<id>)`,
151
+ * with each tool granted to `(AgentRole:agent-security-users#members)`). Written
152
+ * by the Ory Console and the Agent Security broker's enrollment; the plugin only
153
+ * ever resolves it as part of the `use` permit.
154
+ *
155
+ * Its own namespace because a role is not a tool. That is also what makes **one**
156
+ * role tuple enough: while the role was a tool object, a membership had to be
157
+ * written once per tool namespace (Keto matches a subject-set exactly, and the
158
+ * role object's namespace had to be the same as the granting object's), so the
159
+ * same role existed twice and could drift.
160
+ */
161
+ exports.AGENT_ROLE_NAMESPACE = "AgentRole";
162
+ /** Group of registered agent credentials sharing a harness key. */
163
+ exports.AGENT_TYPE_NAMESPACE = "AgentType";
164
+ /** Group of registered sub-agent credentials sharing a type name. */
165
+ exports.SUBAGENT_TYPE_NAMESPACE = "SubAgentType";
166
+ /** The relation naming a role's members. Reads as `User:bob is in members of AgentRole:<role>`. */
167
+ exports.RELATION_MEMBERS = "members";
168
+ /**
169
+ * Namespace for Agent Security enrollment settings — currently whether a newly
170
+ * connected agent's identity is auto-enrolled into the shared role. Stored as an
171
+ * **opt-out** (`Enrollment:autoEnroll#optedOutSubjects@(Enrollment:project#optedOutSubjects)`)
172
+ * so absence means on, which is what a project provisioned before the setting
173
+ * existed reads as. Written and read by the Console and the broker.
174
+ */
175
+ exports.ENROLLMENT_NAMESPACE = "Enrollment";
176
+ /** The object the auto-enroll setting hangs off. */
177
+ exports.AUTO_ENROLL_OBJECT = "autoEnroll";
178
+ /** The relation holding the subjects that opted out of a setting. */
179
+ exports.RELATION_OPTED_OUT_SUBJECTS = "optedOutSubjects";
180
+ /**
181
+ * Namespace for the server-read deny posture. Its own namespace for the same
182
+ * reason as {@link DELEGATION_NAMESPACE}: the posture anchor is not a tool, and
183
+ * once it stops sharing a namespace with tool names it no longer needs a
184
+ * collision-proof sentinel spelling either — hence the plain
185
+ * {@link PERMISSION_MODE_OBJECT} / {@link PROJECT_POSTURE_OBJECT} object ids
186
+ * rather than the double-underscore guards an earlier release used.
187
+ */
188
+ exports.PERMISSION_MODE_NAMESPACE = "PermissionMode";
189
+ /**
190
+ * The object an enforce-mode grant hangs off. The permission mode is resolved as
191
+ * `checkPermission(PermissionMode:mode#enforcedSubjects@<subject>)`.
192
+ */
193
+ exports.PERMISSION_MODE_OBJECT = "mode";
194
+ /**
195
+ * Object standing in for **every principal on the project**, used as the subject
196
+ * of a project-wide posture grant:
197
+ *
198
+ * PermissionMode:mode#enforcedSubjects@(PermissionMode:project#enforcedSubjects)
199
+ *
200
+ * Keto has no wildcard subject, and a per-principal grant cannot cover a
201
+ * principal that does not exist yet — so an agent connecting after an admin
202
+ * turned on enforce would resolve `observe` and silently escape enforcement
203
+ * until someone re-applied the grant for it. Checking this fixed sentinel first
204
+ * makes the posture a property of the project: one relation governs every
205
+ * principal, present and future. Per-principal grants remain meaningful as
206
+ * exceptions (enforce one user while the project is still observing).
207
+ */
208
+ exports.PROJECT_POSTURE_OBJECT = "project";
209
+ /**
210
+ * The permit Argus checks: `users && !blockedSubjects`, evaluated natively by
211
+ * Keto. A verb, as Keto's own `view` is — it names the question the gate asks.
212
+ */
213
+ exports.PERMIT_USE = "use";
214
+ /** Conventional user namespace used for SubjectSet-addressed users (`User:<id>`). */
215
+ const USER_NAMESPACE = "User";
216
+ /**
217
+ * The module OPL sources import their type symbols from. Keto's parser
218
+ * recognizes this name (and the equivalent `@ory/permission-namespace-types`)
219
+ * and strips the import — no npm dependency is pulled at runtime.
220
+ */
221
+ exports.OPL_IMPORT_MODULE = "@ory/keto-namespace-types";
222
+ /**
223
+ * Every type symbol the rendered OPL references, and therefore must import:
224
+ * `Namespace` (every class implements it), `SubjectSet` (the relation unions
225
+ * allow set expansion), and `Context` (the `use` permit is typed
226
+ * `(ctx: Context)`). This is the canonical set — any code that assembles or
227
+ * merges the OPL imports exactly these.
228
+ */
229
+ exports.OPL_IMPORT_SYMBOLS = ["Namespace", "SubjectSet", "Context"];
230
+ /**
231
+ * The principal namespaces every subject is addressed under (SubjectSets):
232
+ * `User`, `Agent`, `SubAgent`, and the `Session` fallback. The object
233
+ * namespaces' relations are typed by them, so they must be defined for checks to
234
+ * resolve rather than `NotFound`.
235
+ *
236
+ * These are **identities** — things that authenticate and that an admin can
237
+ * grant or block. Graph-node references are a separate kind and live in
238
+ * {@link NODE_REFERENCE_NAMESPACES}; keeping the two lists apart is what stops a
239
+ * node id from being written where an identity belongs (#225).
240
+ */
241
+ exports.PRINCIPAL_NAMESPACES = [
242
+ USER_NAMESPACE,
243
+ subject_js_1.AGENT_NAMESPACE,
244
+ subject_js_1.SUBAGENT_NAMESPACE,
245
+ subject_js_1.SESSION_NAMESPACE,
246
+ ];
247
+ /**
248
+ * Namespaces holding references to delegation-graph nodes. Declared so the
249
+ * `delegates` relation that is typed by them resolves, and deliberately *not*
250
+ * part of {@link PRINCIPAL_NAMESPACES}: a node reference is never a grant, block,
251
+ * role-membership, or posture subject.
252
+ */
253
+ exports.NODE_REFERENCE_NAMESPACES = [
254
+ exports.AGENT_NODE_NAMESPACE,
255
+ exports.SUBAGENT_NODE_NAMESPACE,
256
+ ];
257
+ /** Render the canonical import line for a from-scratch OPL file. */
258
+ function renderOplImport() {
259
+ return `import { ${exports.OPL_IMPORT_SYMBOLS.join(", ")} } from "${exports.OPL_IMPORT_MODULE}"`;
260
+ }
261
+ /** Render a principal-namespace class (an empty `implements Namespace`). */
262
+ function renderPrincipalClass(name) {
263
+ return `class ${name} implements Namespace {}`;
264
+ }
265
+ /**
266
+ * Render a tool-namespace class body: the `users` grant, the `blockedSubjects`
267
+ * deny-override, and the `use` permit (`users && !blockedSubjects`) Argus
268
+ * checks. The single definition of that class, used for both tool namespaces.
269
+ */
270
+ function renderToolNamespaceClass(namespace = exports.DEFAULT_NAMESPACE) {
271
+ return `class ${namespace} implements Namespace {
272
+ related: {
273
+ // Machine principals are grantable in their own right: \`${subject_js_1.AGENT_NAMESPACE}:<clientId>\` is
274
+ // one session's registered agent credential and \`${subject_js_1.SUBAGENT_NAMESPACE}:<clientId>\` one
275
+ // typed sub-agent credential in that session. Their narrower session- and spawn-scoped forms
276
+ // (\`${subject_js_1.AGENT_NAMESPACE}:<clientId>|<session>\`, \`${subject_js_1.SUBAGENT_NAMESPACE}:<clientId>|<session>|<type>\`) are
277
+ // members of the same namespaces, which is what lets a block name one run.
278
+ // A grant may also name the shared role, so one membership covers every
279
+ // tool in every tool namespace: \`@(${exports.AGENT_ROLE_NAMESPACE}:<role>#${exports.RELATION_MEMBERS})\`.
280
+ ${exports.RELATION_USERS}: (${USER_NAMESPACE} | ${subject_js_1.SESSION_NAMESPACE} | ${subject_js_1.AGENT_NAMESPACE} | ${subject_js_1.SUBAGENT_NAMESPACE} | SubjectSet<${exports.AGENT_ROLE_NAMESPACE}, "${exports.RELATION_MEMBERS}"> | SubjectSet<${namespace}, "${exports.RELATION_USERS}">)[]
281
+ ${exports.RELATION_BLOCKED_SUBJECTS}: (${USER_NAMESPACE} | ${subject_js_1.SESSION_NAMESPACE} | ${subject_js_1.AGENT_NAMESPACE} | ${subject_js_1.SUBAGENT_NAMESPACE} | SubjectSet<${exports.AGENT_ROLE_NAMESPACE}, "${exports.RELATION_MEMBERS}"> | SubjectSet<${namespace}, "${exports.RELATION_BLOCKED_SUBJECTS}">)[]
282
+ }
283
+
284
+ permits = {
285
+ // Deny-override: any matching \`${exports.RELATION_BLOCKED_SUBJECTS}\` relation beats a
286
+ // \`${exports.RELATION_USERS}\` grant, regardless of how the grant was reached (direct or
287
+ // via a set).
288
+ ${exports.PERMIT_USE}: (ctx: Context): boolean =>
289
+ this.related.${exports.RELATION_USERS}.includes(ctx.subject) &&
290
+ !this.related.${exports.RELATION_BLOCKED_SUBJECTS}.includes(ctx.subject),
291
+ }
292
+ }`;
293
+ }
294
+ /**
295
+ * Render the delegation-node class: who delegated to this node, and which OAuth2
296
+ * clients it ran as. Nothing is ever checked here — the graph is an audit trail —
297
+ * but the relations must be declared for the broker's writes to be accepted.
298
+ */
299
+ function renderDelegationNamespaceClass(namespace = exports.DELEGATION_NAMESPACE) {
300
+ return `class ${namespace} implements Namespace {
301
+ related: {
302
+ // A delegation edge's subject is whoever delegated. For a user that is the
303
+ // identity itself (${USER_NAMESPACE}:<id>); for a machine it is a *reference to the
304
+ // delegating node* — \`${exports.AGENT_NODE_NAMESPACE}:<identity>|<harness>|<host>|<session>\`, not the
305
+ // OAuth2 client id. Those are different kinds of thing and no longer share a
306
+ // namespace with the grant/block identities (#225), so a node id that ends up
307
+ // written as a grant subject is visibly the wrong kind rather than being
308
+ // spelled exactly like a correct one. (Keto still stores such a tuple — it
309
+ // does not type-check subjects on write — it just never matches anything.)
310
+ ${exports.RELATION_DELEGATES}: (${USER_NAMESPACE} | ${exports.AGENT_NODE_NAMESPACE} | ${exports.SUBAGENT_NODE_NAMESPACE})[]
311
+ // Which OAuth2 clients this node ran as. Recorded for audit, never checked —
312
+ // the node key survives credential rotation, so this is the only place the
313
+ // credential that actually acted is written down.
314
+ ${exports.RELATION_CREDENTIALS}: (${subject_js_1.AGENT_NAMESPACE} | ${subject_js_1.SUBAGENT_NAMESPACE})[]
315
+ }
316
+ }`;
317
+ }
318
+ /**
319
+ * Render the role class: the members of a shared role a project grants tools to.
320
+ * One class, one relation — a role is a group of subjects and nothing else.
321
+ */
322
+ function renderAgentRoleNamespaceClass(namespace = exports.AGENT_ROLE_NAMESPACE) {
323
+ return `class ${namespace} implements Namespace {
324
+ related: {
325
+ // The subjects that belong to the role. A tool grant names
326
+ // \`(${namespace}:<role>#${exports.RELATION_MEMBERS})\` as its subject, so adding a member grants
327
+ // every tool the role holds, in either tool namespace, from one relation.
328
+ ${exports.RELATION_MEMBERS}: (${USER_NAMESPACE} | ${subject_js_1.AGENT_NAMESPACE} | ${subject_js_1.SUBAGENT_NAMESPACE} | SubjectSet<${namespace}, "${exports.RELATION_MEMBERS}">)[]
329
+ }
330
+ }`;
331
+ }
332
+ /** Render a harness/sub-agent type group whose members inherit posture. */
333
+ function renderAgentTypeNamespaceClass(namespace, memberNamespace) {
334
+ return `class ${namespace} implements Namespace {
335
+ related: {
336
+ ${exports.RELATION_MEMBERS}: (${memberNamespace} | SubjectSet<${namespace}, "${exports.RELATION_MEMBERS}">)[]
337
+ }
338
+ }`;
339
+ }
340
+ /**
341
+ * Render the enrollment-settings class: which subjects opted out of a setting.
342
+ * An opt-out rather than an opt-in, so absence reads as "on".
343
+ */
344
+ function renderEnrollmentNamespaceClass(namespace = exports.ENROLLMENT_NAMESPACE) {
345
+ return `class ${namespace} implements Namespace {
346
+ related: {
347
+ // Auto-enrollment of a newly connected agent's identity into the shared role
348
+ // is on unless opted out, so the setting is stored as an opt-out on the
349
+ // \`${exports.AUTO_ENROLL_OBJECT}\` object. Absence means on — which is also how a project
350
+ // provisioned before the setting existed reads.
351
+ ${exports.RELATION_OPTED_OUT_SUBJECTS}: (${USER_NAMESPACE} | SubjectSet<${namespace}, "${exports.RELATION_OPTED_OUT_SUBJECTS}">)[]
352
+ }
353
+ }`;
354
+ }
355
+ /**
356
+ * Render the permission-mode class: the set of subjects an admin has promoted
357
+ * from `observe` to `enforce`. One relation, read by every gate.
358
+ */
359
+ function renderPermissionModeNamespaceClass(namespace = exports.PERMISSION_MODE_NAMESPACE) {
360
+ return `class ${namespace} implements Namespace {
361
+ related: {
362
+ // An admin grants \`${exports.RELATION_ENFORCED_SUBJECTS}\` on the \`${exports.PERMISSION_MODE_OBJECT}\` object to
363
+ // promote a subject (or a whole group, via a SubjectSet) from the default
364
+ // \`observe\` posture to \`enforce\`. The \`${exports.PROJECT_POSTURE_OBJECT}\` sentinel subject-set is
365
+ // how a posture is set for the whole project, including principals that do
366
+ // not exist yet. Sub-agents are promotable in their own right.
367
+ ${exports.RELATION_ENFORCED_SUBJECTS}: (${USER_NAMESPACE} | ${subject_js_1.SESSION_NAMESPACE} | ${subject_js_1.AGENT_NAMESPACE} | ${subject_js_1.SUBAGENT_NAMESPACE} | SubjectSet<${exports.AGENT_TYPE_NAMESPACE}, "${exports.RELATION_MEMBERS}"> | SubjectSet<${exports.SUBAGENT_TYPE_NAMESPACE}, "${exports.RELATION_MEMBERS}"> | SubjectSet<${namespace}, "${exports.RELATION_ENFORCED_SUBJECTS}">)[]
368
+ }
369
+ }`;
370
+ }
371
+ /**
372
+ * Render the complete OPL namespace-definition file for the Agent Security
373
+ * model.
374
+ *
375
+ * This is the model an Ory project must have for Agent Security checks to
376
+ * resolve. On a hosted project it is applied in the Ory Console (Agent
377
+ * Security) — the plugin never pushes it. This renderer exists so the **local
378
+ * dev stack** can mount the identical definition into its own Keto config,
379
+ * keeping local and hosted on one definition, and so the model can be printed
380
+ * for reference.
381
+ *
382
+ * `namespace` is the tool namespace (OPL class name); defaults to
383
+ * {@link DEFAULT_NAMESPACE}. Operators who override `ORY_PERMISSION_NAMESPACE`
384
+ * must provision the schema under the same name so the class the checks address
385
+ * exists. The `Delegation` and `PermissionMode` namespaces are not
386
+ * operator-configurable — they hold project-wide state, not per-deployment tool
387
+ * objects.
388
+ *
389
+ * The output is the TypeScript-flavored OPL source Keto parses (the import is
390
+ * recognized and stripped by Keto's parser — no npm dependency is pulled at
391
+ * runtime). It is written verbatim into the local stack's keto config mount.
392
+ */
393
+ function buildAgentSecurityOpl(namespace = exports.DEFAULT_NAMESPACE, shellNamespace = exports.DEFAULT_SHELL_NAMESPACE) {
394
+ return [
395
+ renderOplImport(),
396
+ "",
397
+ "// Principal namespaces. Every principal — user, agent, sub-agent, and the",
398
+ `// \`${subject_js_1.SESSION_NAMESPACE}:<id>\` fallback subject — is addressed as a SubjectSet`,
399
+ `// (\`${USER_NAMESPACE}:<id>\`, \`${subject_js_1.AGENT_NAMESPACE}:<id>\`, …). They must exist so the object`,
400
+ "// namespaces' relations, which are typed by them, resolve.",
401
+ ...exports.PRINCIPAL_NAMESPACES.map(renderPrincipalClass),
402
+ "",
403
+ "// Delegation-graph node references. Distinct from the principal namespaces",
404
+ "// above because a node reference is not an identity: it is a pointer at a",
405
+ `// node keyed \`<identity>|<harness>|<host>|<session>\`, and it is only ever the`,
406
+ `// subject of \`${exports.RELATION_DELEGATES}\`. Keeping it out of \`${subject_js_1.AGENT_NAMESPACE}\` / \`${subject_js_1.SUBAGENT_NAMESPACE}\` means a`,
407
+ "// node id written as a grant, block, role, or posture subject names a class",
408
+ "// that has no such relation, rather than storing a tuple that never matches.",
409
+ ...exports.NODE_REFERENCE_NAMESPACES.map(renderPrincipalClass),
410
+ "",
411
+ `// Harness tool objects (\`${namespace}:Bash\`). \`${exports.RELATION_USERS}\` is the set of subjects`,
412
+ `// granted the tool; \`${exports.RELATION_BLOCKED_SUBJECTS}\` is an explicit deny-override. The`,
413
+ `// \`${exports.PERMIT_USE}\` permit is the effective decision Argus checks: granted AND not`,
414
+ "// blocked.",
415
+ renderToolNamespaceClass(namespace),
416
+ "",
417
+ "// Shell command sub-tools (issue #76): each program/builtin word a shell",
418
+ `// command runs is checked as \`${shellNamespace}:<word>#${exports.PERMIT_USE}\`, so a blocked`,
419
+ '// binary (e.g. curl) can\'t be reached through `bash -c "curl …"`. Same',
420
+ "// grant / block / use model as the tool namespace above.",
421
+ renderToolNamespaceClass(shellNamespace),
422
+ "",
423
+ "// The shared role a project grants tools to, so an admin adds a member",
424
+ "// instead of re-granting every tool per identity.",
425
+ renderAgentRoleNamespaceClass(),
426
+ "",
427
+ "// Delegation nodes: the current-state graph of who delegated to whom, written",
428
+ "// by the agent-security broker and read by the Console. Audit only — no",
429
+ "// permission check resolves against it.",
430
+ renderDelegationNamespaceClass(),
431
+ "",
432
+ "// Registered credential groups used to apply posture by harness or sub-agent type.",
433
+ renderAgentTypeNamespaceClass(exports.AGENT_TYPE_NAMESPACE, subject_js_1.AGENT_NAMESPACE),
434
+ "",
435
+ renderAgentTypeNamespaceClass(exports.SUBAGENT_TYPE_NAMESPACE, subject_js_1.SUBAGENT_NAMESPACE),
436
+ "",
437
+ "// Server-read deny posture: which subjects are in `enforce` rather than the",
438
+ "// default `observe`. Set in the Ory Console, read by every gate.",
439
+ renderPermissionModeNamespaceClass(),
440
+ "",
441
+ "// Agent Security enrollment settings, stored as opt-outs so absence reads as",
442
+ "// the default (on). Written and read by the Console and the broker.",
443
+ renderEnrollmentNamespaceClass(),
444
+ "",
445
+ ].join("\n");
446
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Server-read permission mode.
3
+ *
4
+ * `observe` vs `enforce` is no longer a local, per-machine setting — it is a
5
+ * **permission the plugin reads from Keto** before gating a tool call, so an
6
+ * admin controls the deny posture centrally from the Ory project (Ory Console /
7
+ * Agent Security), and every session inherits it. There is no local override
8
+ * and no `ORY_PERMISSION_MODE` env var.
9
+ *
10
+ * The mode lives in its own `PermissionMode` namespace — it is not a tool, so it
11
+ * is not a tool object (see the naming-conventions note in `opl.ts`). It is
12
+ * resolved by `checkPermission` against the `mode` anchor, at two scopes:
13
+ *
14
+ * project: PermissionMode:mode#enforcedSubjects@(PermissionMode:project#enforcedSubjects)
15
+ * applicable: PermissionMode:mode#enforcedSubjects@<identity|agent|subagent>
16
+ * allowed ⇒ enforce denied ⇒ observe
17
+ *
18
+ * The **project** grant is checked first and is the posture an admin normally
19
+ * sets: one tuple governs every principal, including ones that do not exist
20
+ * yet. That matters because a per-principal grant cannot cover a future
21
+ * principal — before the project scope existed, an agent connecting after an
22
+ * admin turned on enforce resolved `observe` and silently escaped enforcement
23
+ * until someone re-applied the grant for it. A **per-principal** grant remains
24
+ * meaningful as an exception: enforce one identity, agent harness, or sub-agent
25
+ * type while the project still observes. Type grants are inherited through the
26
+ * server-managed AgentType/SubAgentType membership relations.
27
+ * Neither scope can be set by the runtime — the plugin only ever reads, riding
28
+ * its own OAuth2 access token like every other Keto read.
29
+ *
30
+ * Resolution precedence, applied by {@link resolvePermissionMode}:
31
+ *
32
+ * 1. **In-memory TTL cache** (per scope) — avoids a Keto round-trip on
33
+ * literally every tool call while still re-reading the server every `ttlMs()`.
34
+ * The project scope caches under one key shared by every subject, so the
35
+ * common "whole project enforced" case costs a single read per TTL window.
36
+ * 2. **Server check** — project scope, then identity and acting machine scopes
37
+ * only if the project is not enforcing. On a clean result, refresh the in-memory cache
38
+ * and return it (`source: "server"`).
39
+ * 3. **Persistent cache** — when the server is unreachable, fall back to the
40
+ * last value read from the server, persisted in `config.json`
41
+ * (`source: "cache"`).
42
+ * 4. **`observe`** — nothing was ever cached (`source: "default"`).
43
+ *
44
+ * The persistent cache is written by {@link warmPermissionModeCache} at session
45
+ * start and when a gate first discovers a new acting-machine scope. It only
46
+ * writes when a cached mode changes, so normal per-tool checks do not churn the
47
+ * shared config while child-only posture still survives subprocess boundaries.
48
+ */
49
+ import type { OryAgentClient } from "./client.js";
50
+ import { type PermissionMode } from "./config.js";
51
+ import type { PermissionCheck } from "./types.js";
52
+ /** Where {@link resolvePermissionMode} sourced the value it returned. */
53
+ export type PermissionModeSource = "server" | "cache" | "default";
54
+ /** The subject a mode check is evaluated for — the same shape as a tool check. */
55
+ export type ModeSubject = Pick<PermissionCheck, "subjectId" | "subjectSet">;
56
+ export interface ResolvedPermissionMode {
57
+ mode: PermissionMode;
58
+ source: PermissionModeSource;
59
+ }
60
+ export interface AdditionalModeSubject {
61
+ subject: ModeSubject;
62
+ scope: "agent" | "subagent";
63
+ }
64
+ /** Drop all in-memory caches. Test-only (reassigns the WeakMap). */
65
+ export declare function resetPermissionModeCache(): void;
66
+ /**
67
+ * Resolve the current permission mode for `subject`. Never throws — a Keto
68
+ * error falls back to the persistent cache, then `observe`. See the module
69
+ * doc for the precedence. Does **not** persist (that is
70
+ * {@link warmPermissionModeCache}'s job); `opts.now` is injectable for tests.
71
+ */
72
+ export declare function resolvePermissionMode(client: OryAgentClient, subject: ModeSubject, opts?: {
73
+ now?: number;
74
+ additionalSubjects?: AdditionalModeSubject[];
75
+ }): Promise<ResolvedPermissionMode>;
76
+ /**
77
+ * Resolve the mode and, when it came from a live server read, persist it to
78
+ * `config.json` so a subprocess harness's later hook processes (and the
79
+ * synchronous MCP `applyPermissionMode` path) see the current posture, and so a
80
+ * subsequent offline session has a cached value to fall back to. Called once at
81
+ * session start. Best-effort; never throws. Only writes when the value changed,
82
+ * so it doesn't churn the config lockfile.
83
+ */
84
+ export declare function warmPermissionModeCache(client: OryAgentClient, subject: ModeSubject, opts?: {
85
+ now?: number;
86
+ additionalSubjects?: AdditionalModeSubject[];
87
+ }): Promise<ResolvedPermissionMode>;