@owlmeans/iam 0.1.18-rc.3 → 0.1.18-rc.31

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 (48) hide show
  1. package/README.md +2 -2
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/iam/SKILL.md +193 -56
  4. package/build/consts.d.ts +91 -0
  5. package/build/consts.d.ts.map +1 -1
  6. package/build/consts.js +97 -0
  7. package/build/consts.js.map +1 -1
  8. package/build/errors.d.ts +13 -0
  9. package/build/errors.d.ts.map +1 -1
  10. package/build/errors.js +17 -0
  11. package/build/errors.js.map +1 -1
  12. package/build/gate/index.d.ts +4 -0
  13. package/build/gate/index.d.ts.map +1 -0
  14. package/build/gate/index.js +4 -0
  15. package/build/gate/index.js.map +1 -0
  16. package/build/gate/param.d.ts +31 -0
  17. package/build/gate/param.d.ts.map +1 -0
  18. package/build/gate/param.js +99 -0
  19. package/build/gate/param.js.map +1 -0
  20. package/build/gate/resolve.d.ts +10 -0
  21. package/build/gate/resolve.d.ts.map +1 -0
  22. package/build/gate/resolve.js +93 -0
  23. package/build/gate/resolve.js.map +1 -0
  24. package/build/gate/validate.d.ts +15 -0
  25. package/build/gate/validate.d.ts.map +1 -0
  26. package/build/gate/validate.js +107 -0
  27. package/build/gate/validate.js.map +1 -0
  28. package/build/index.d.ts +2 -0
  29. package/build/index.d.ts.map +1 -1
  30. package/build/index.js +2 -0
  31. package/build/index.js.map +1 -1
  32. package/build/permission.d.ts +21 -0
  33. package/build/permission.d.ts.map +1 -0
  34. package/build/permission.js +39 -0
  35. package/build/permission.js.map +1 -0
  36. package/build/types.d.ts +251 -6
  37. package/build/types.d.ts.map +1 -1
  38. package/package.json +7 -6
  39. package/src/consts.ts +105 -0
  40. package/src/errors.ts +19 -0
  41. package/src/gate/index.ts +3 -0
  42. package/src/gate/param.ts +122 -0
  43. package/src/gate/resolve.ts +114 -0
  44. package/src/gate/validate.ts +132 -0
  45. package/src/index.ts +2 -0
  46. package/src/permission.ts +48 -0
  47. package/src/types.ts +283 -6
  48. package/tests/gate-param.spec.ts +234 -0
package/README.md CHANGED
@@ -13,7 +13,7 @@ Provider-agnostic IAM abstraction for the OwlMeans framework — `IamService` in
13
13
  ## Installation
14
14
 
15
15
  ```bash
16
- bun add @owlmeans/iam
16
+ bun add @owlmeans/iam@^0.1.18-rc.31
17
17
  ```
18
18
 
19
19
  ## Usage
@@ -36,7 +36,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
36
36
  your project's skill store (`.agents/skills/`):
37
37
 
38
38
  ```sh
39
- npx @owlmeans/agent-skills
39
+ npx @owlmeans/agent-skills@^0.1.18-rc.27
40
40
  ```
41
41
 
42
42
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/iam",
4
- "version": "0.1.18-rc.0",
5
- "generatedAt": "2026-08-16T22:20:50.504Z",
4
+ "version": "0.1.18-rc.31",
5
+ "generatedAt": "2026-09-15T13:20:58.250Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: iam
3
- description: "IamService abstraction for provider-agnostic IAM provisioning. Load when wiring IAM operations, switching IAM mode, or implementing a new IAM provider. Applies to files matching **/services/iam*.ts, **/context.ts, **/types.ts."
3
+ description: "How to use @owlmeans/iam — the provider-agnostic IamService, the permission-definition and two-form grant model, client-id uniqueness, issuer and redirect-URI rules, and hasPermission. Load when wiring IAM operations, granting or revoking permissions, selecting the IAM backend, or implementing a new IAM provider. Applies to files matching **/services/iam*.ts, **/context.ts, **/types.ts."
4
4
  metadata:
5
5
  applyTo: "**/services/iam*.ts, **/context.ts, **/types.ts"
6
6
  ---
@@ -8,7 +8,40 @@ metadata:
8
8
 
9
9
  # Using `@owlmeans/iam`
10
10
 
11
- Provider-agnostic IAM abstraction. Defines `IamService` interface and related types. Used by both the platform backend (`viable-backend`) and the agent library (`@owlmeans/viable`). Concrete implementations live in `@owlmeans/iam-keycloak` (full proxy) and `@owlmeans/iam-integrated`.
11
+ **Install:** `"@owlmeans/iam": "^0.1.18-rc.31"` in `dependencies`
12
+
13
+ Provider-agnostic IAM abstraction: the `IamService` interface, the permission and grant shapes, the
14
+ gate-param grammar, and `hasPermission`. It contains no implementation and talks to no provider —
15
+ concrete backends (a Keycloak proxy, an integrated backend built on the OwlMeans OIDC provider) are
16
+ separate packages, and every consumer depends on this interface rather than on one of them.
17
+
18
+ ## The browser half — `@owlmeans/client-iam`
19
+
20
+ `@owlmeans/client-iam` has no skill of its own because it is a wiring package: it re-exports these
21
+ types and `hasPermission`, and adds the two calls that turn "this application uses IAM" into two
22
+ lines.
23
+
24
+ | Export | Description |
25
+ |--------|-------------|
26
+ | `appendIam(context, opts?)` | Wires the OIDC guard onto a web context **and** installs the consent-before-sign-in precondition |
27
+ | `withIamGuard(protocolTree, coguards?)` | Returns a decorated immutable protocol tree with IAM's OIDC guard applied |
28
+ | `iamEntrypoints(dispatcherProps?)` | Browser-local OIDC bindings and dispatcher-screen binding |
29
+ | `requireConsentForLogin(ctx, opts?)` | That precondition on its own, for a context wired some other way |
30
+ | `CONSENT_LOGIN_PRECONDITION` / `CLIENT_IAM_SERVICE` | The precondition alias and the service alias |
31
+ | `useLogin` / `useLogout`, `LoginOutcome` / `LoginIntent`, `LoginPlugin` / `LoginService` / … | Re-exported sign-in surface, so an app has one IAM import rather than three |
32
+ | Everything from `@owlmeans/iam` | The types above, plus `hasPermission` |
33
+
34
+ The protocol tree is immutable. Decorate it once with `withIamGuard(protocols)` before binding local
35
+ screens and API callers, then spread `iamEntrypoints()` once into that browser binding list. The
36
+ helper deliberately does not add declarations to a flattened entrypoint array.
37
+
38
+ And the consent precondition sits on `LoginService.begin` rather than on a hook, a screen or a login
39
+ plugin, because `begin` is the single funnel every sign-in mechanic passes through and the one place
40
+ the user's gesture is still live — a refusal resolves as `LoginOutcome.Gesture` and opens the consent dialog in
41
+ the same gesture. An application that genuinely sets no cookie passes `{ consent: { disabled: true } }`.
42
+
43
+ The server half of the same story is `@owlmeans/server-iam` — the IAM gate that asserts the grants
44
+ this package's screens hand out. Permission decisions are made there, never in the browser.
12
45
 
13
46
  ## Public API surface
14
47
 
@@ -18,21 +51,38 @@ Provider-agnostic IAM abstraction. Defines `IamService` interface and related ty
18
51
  | `IamClient` | type | Provisioned OIDC client `{ id?, clientId, secret?, name?, realm? }` |
19
52
  | `IamClientOptions` | type | `{ redirectUris? }` — explicit `ensureClient` hardening; omitting is a keycloak-only legacy shape (see below) |
20
53
  | `IamCredentialsPair` | type | `{ token: string; realm: string }` |
21
- | `IamPermissionArgs` | type | `{ permission?, resourceScoped?, title? }` — `permission` absent means unscoped resource name |
54
+ | `IamPermissionArgs` | type | `{ permission?, resourceScoped?, title?, area?, managed? }` — `permission` absent means unscoped resource name |
22
55
  | `IamResourceSpec` | type | `{ name: string; displayName?: string }` |
23
- | `IamPermissionDefinition` | type | Declared permission `{ name, resource, action?, resourceScoped?, title? }` |
24
- | `IamGrantArgs` | type | `{ resources?: string[] }` — present = resource-scoped grant form |
25
- | `IamGrant` | type | `{ profileId, clientId, permission, resources? }` |
56
+ | `IamPermissionDefinition` | type | Declared permission `{ name, resource, action?, resourceScoped?, title?, area?, managed? }` |
57
+ | `IamPermissionFilter` | type | `{ areas?, managed?, resourceScoped? }` — narrows `listPermissions`; an `areas` entry of `null` matches untagged definitions |
58
+ | `IamGrantArgs` | type | `{ resources?: string[], mode? }` |
59
+ | `IamGrant` | type | `{ profileId, clientId, permission, resources?, mode? }` |
60
+ | `IamGrantMode` | enum | `Blanket` / `Resources` / `All` — which FORM of a grant an operation addresses |
61
+ | `IamGrantBundle` | type | `{ filter?, permissions?, mode?, resources? }` — what `grantBundle` hands out |
62
+ | `IamRemovalPolicy` | enum | `Cascade` / `Refuse` — what to do about grants of a definition being deleted |
63
+ | `IamPermissionDeleteArgs`, `IamPermissionRemoval` | type | `{ policy?, managed? }` and `{ permission, clientId, found, revoked }` |
64
+ | `IamNormalizeArgs`, `IamNormalizationReport` | type | `{ dryRun? }` and the rename/merge plan `normalizePermissions` reports |
65
+ | `IAM_AREAS`, `IamArea` | const/type | `['user', 'operator', 'admin']` — presentational grouping tags, read by no gate |
66
+ | `PERMISSION_ACTION_SEPARATOR` | const | `'--'` — TWO hyphens, never one |
67
+ | `parsePermissionName` / `composePermissionName` / `isPermissionName` | fn | Read, build and validate a permission NAME |
68
+ | `ParsedPermissionName` | type | `{ name, resource, action?, problem? }` |
69
+ | `IamUser` | type | End-user of an entity `{ profileId, email?, name?, role, disabled?, grantCount? }` |
70
+ | `IamUserInvite` | type | `{ email, name?, role? }` — find-or-create args |
71
+ | `IamUserUpdate` | type | `{ name?, role?, disabled? }` |
26
72
  | `hasPermission` | fn | `(auth, permission, { scope?, resourceId? }?)` — checks `Authorization.permissions` PermissionSet[]; an unscoped set satisfies a resourceId check |
73
+ | `HasPermissionOptions` | type | `{ scope?, resourceId? }` |
74
+ | Gate grammar | fn/type | `parseGateParam`, `parseGateSelector`, `formatGateParam`, `resolveGateResource`, `validateGateParams`, `routeParamsOf`, plus `RESOURCE_*_SEPARATOR`, `GateParamSource`, `DEFAULT_GATE_PARAM_SOURCES`, `GateParamErrorCode`, `GateResolutionFailure` and the `Gate*` types. The syntax itself is documented in the `server-iam` skill |
27
75
  | `DEFAULT_ALIAS` | const | Default service alias `'iam-service'` |
28
76
  | `IAM_MODE_KEYCLOAK` | const | `'keycloak'` |
29
77
  | `IAM_MODE_INTEGRATED` | const | `'integrated'` |
30
78
  | `IamMode` | type | `'keycloak' \| 'integrated'` |
31
79
  | `IamError` | class | Base IAM error |
32
- | `IamClientError` | class | Thrown when an OIDC client is missing a required field (e.g. `secret`) |
33
- | `IamResourceError` | class | Thrown when a KC resource returns with no name |
80
+ | `IamClientError` | class | An OIDC client is invalid or unusable — missing `secret`, unusable `redirect_uris`, `client:entity-mismatch`, an unconfigured provider route |
81
+ | `IamResourceError` | class | A provider resource returned with no name |
34
82
  | `IamGrantError` | class | Grant subject missing or entity mismatch |
35
- | `IamUnsupported` | class | Operation not supported by the active backend (e.g. resource-scoped grants on Keycloak) |
83
+ | `IamPermissionError` | class | A refusal about a DEFINITION: `permission:held:<name>`, `permission:managed:<name>` |
84
+ | `IamUserError` | class | An end-user operation was refused |
85
+ | `IamUnsupported` | class | Operation not supported by the active backend (e.g. `resource-scoped-grant`, `user-management`) |
36
86
 
37
87
  ## Permission model (two grant forms)
38
88
 
@@ -46,57 +96,120 @@ subjects with `grantPermission`:
46
96
  Grants materialize as OwlMeans `PermissionSet[]` (`scope` = clientId; resource-scoped grants live in a
47
97
  dedicated set per permission carrying `resources[]`, because `resources` applies to all keys of a set).
48
98
 
99
+ A **name** is `<resource>--<action>`, with two hyphens. One hyphen is not a separator: `enquiry-view`
100
+ is a resource named `enquiry-view` with no action, and it must keep parsing that way, because a name
101
+ already granted somewhere cannot be reinterpreted without orphaning the grant. Build and read names
102
+ through `composePermissionName` / `parsePermissionName` rather than splitting strings.
103
+
104
+ `resourceScoped: true` says a permission *may* be bound to resource ids, not that it must be. The
105
+ same name is grantable in both forms and they are stored separately, which is what `IamGrantMode`
106
+ addresses: `Blanket` is the set carrying no `resources` (and `hasPermission` reads it as covering
107
+ every id), `Resources` is an explicit id list, and `All` is revoke-only. The defaults are asymmetric
108
+ on purpose — `grantPermission` defaults to `Blanket`, `revokePermission` to `All`, because a bare
109
+ revoke has always meant "remove it everywhere". Pass `mode` explicitly rather than relying on either.
110
+
111
+ `area` is a presentational grouping tag (`IAM_AREAS`, or any string a deployment defines) and decides
112
+ nothing at request time. `managed: true` marks a definition the platform owns: an operator cannot
113
+ revoke it from an undifferentiated list, and a repair that deletes "definitions no declaration names"
114
+ must not reach one.
115
+
49
116
  ## `IamService` interface
50
117
 
51
118
  ```ts
52
119
  interface IamService extends InitializedService {
53
- // Get the admin OIDC provider config for a tenant (used in payment provisioning, RP config)
120
+ // The tenant's admin OIDC provider config, and a raw admin { token, realm } pair.
54
121
  getEntityAdminConfig: (entityId: string) => Promise<OidcProviderConfig>
55
-
56
- // Get an admin { token, realm } pair for raw low-level calls (avoid unless necessary)
57
122
  getCredentialsPair: (entityId: string) => Promise<IamCredentialsPair>
58
123
 
59
- // The tenant's public, fully-qualified OIDC issuer URL — the single value a relying party needs.
60
- // Config-only (no remote call); throws IamClientError when the provider route is unconfigured.
124
+ // The tenant's public issuer URL — see "Issuer & redirect URIs".
61
125
  getIssuerUrl: (entityId: string) => Promise<string>
62
126
 
63
- // Provision an OIDC client for a tenant and return its credentials.
64
- // options.redirectUris hardens the client; always pass them (see "Issuer & redirect URIs").
127
+ // Provision an OIDC client for a tenant — see "Issuer & redirect URIs" and
128
+ // "A client id is a global name" for the two rules it has to obey.
65
129
  ensureClient: (entityId: string, clientId: string, options?: IamClientOptions) => Promise<IamClient>
66
130
 
67
- // Provision a named permission on a client resource; returns the resource name
131
+ // Reserve a free client id without provisioning it; false when anyone else holds it.
132
+ claimClient: (entityId: string, clientId: string) => Promise<boolean>
133
+
134
+ // Release a client id and everything keyed by it (called when a project/slot is deleted).
135
+ deleteClient: (entityId: string, clientId: string) => Promise<void>
136
+
137
+ // Declare a permission. Returns the canonical name. MERGES into an existing definition —
138
+ // resource/action re-derived, omitted flags kept — and NEVER rewrites the name. Refuses a
139
+ // `resource` carrying an `@` selector.
68
140
  ensurePermission: (entityId: string, clientId: string, resource?: string, args?: IamPermissionArgs) => Promise<string>
69
141
 
70
- // Provision a resource and assign it to the tenant's owner role (used in payment provisioning)
142
+ // Remove definitions BY NAME, and by default their grants. Idempotent ({ found: false } when
143
+ // absent). Grants go first: a crash between the stores must leave a definition with no grants,
144
+ // never grants with no definition. A `managed` definition needs args.managed === true.
145
+ deletePermission: (entityId: string, clientId: string, permission: string | string[], args?: IamPermissionDeleteArgs) => Promise<IamPermissionRemoval[]>
146
+
147
+ // Repair definitions whose stored name leaked a gate selector, moving grants in the same write.
148
+ // A rename and a merge — never a delete, never an invention.
149
+ normalizePermissions: (entityId: string, clientId: string, args?: IamNormalizeArgs) => Promise<IamNormalizationReport>
150
+
151
+ // Provision a resource and assign it to the tenant's owner role
71
152
  ensureResourceOwnership: (entityId: string, clientId: string, resource: IamResourceSpec) => Promise<void>
72
153
 
73
154
  // --- Authorization (permission definitions & grants) ---
74
155
 
75
- // List permission definitions registered for the entity's client
76
- listPermissions: (entityId: string, clientId: string) => Promise<IamPermissionDefinition[]>
156
+ listPermissions: (entityId: string, clientId: string, filter?: IamPermissionFilter) => Promise<IamPermissionDefinition[]>
77
157
 
78
- // Grant a permission to an end-user subject; args.resources = resource-scoped form
158
+ // args.resources selects the resource-scoped form; see "Permission model (two grant forms)"
79
159
  grantPermission: (entityId: string, clientId: string, profileId: string, permission: string, args?: IamGrantArgs) => Promise<IamGrant>
80
-
81
- // Revoke; with args.resources only those ids are removed, else the whole grant
82
160
  revokePermission: (entityId: string, clientId: string, profileId: string, permission: string, args?: IamGrantArgs) => Promise<void>
83
161
 
84
- // List grants for the client, optionally for one subject
162
+ // Grant everything a bundle selects to each subject, idempotently, in ONE read and write per
163
+ // subject. Returns what each subject now holds, not what changed. Use it instead of looping over
164
+ // grantPermission, which costs N·M writes and N·M lost-update windows on the same records.
165
+ grantBundle: (entityId: string, clientId: string, profileIds: string[], bundle: IamGrantBundle) => Promise<IamGrant[]>
166
+
85
167
  listGrants: (entityId: string, clientId: string, profileId?: string) => Promise<IamGrant[]>
168
+
169
+ // --- End-user management — see "End-users are customer-wide" ---
170
+
171
+ listUsers: (entityId: string, clientId?: string) => Promise<IamUser[]>
172
+ getUser: (entityId: string, profileId: string) => Promise<IamUser | null>
173
+ inviteUser: (entityId: string, invite: IamUserInvite) => Promise<IamUser>
174
+ updateUser: (entityId: string, profileId: string, update: IamUserUpdate) => Promise<IamUser>
175
+ removeUser: (entityId: string, profileId: string) => Promise<void>
86
176
  }
87
177
  ```
88
178
 
89
- ## `IamClient.realm` field
179
+ ## End-users are customer-wide
90
180
 
91
- `realm` is set by the provider to the entity ID. Use it instead of accessing internal properties:
181
+ An end-user belongs to the **entity**, not to a project: one customer's users are shared across every
182
+ project they own. `listUsers` therefore returns the entity's whole set and `clientId` only scopes the
183
+ reported `grantCount` to one project's client. Do not reintroduce a per-client filter — a person who
184
+ has authenticated against a project but holds no grant there is still that project's user, and the
185
+ screen that hands out grants is exactly where they have to be visible.
92
186
 
93
- ```ts
94
- // ✓ correct
95
- await meta.update('oidcRealm', client.realm ?? fallbackEntityId)
187
+ `inviteUser` is idempotent by email, which is what lets an invitation and a first sign-in converge on
188
+ one record instead of accumulating twins. A backend with no user store of its own — one where the
189
+ customer manages users in the provider's own console — throws `IamUnsupported('user-management')`
190
+ from all five methods.
96
191
 
97
- // ✗ never do this — internal Keycloak implementation detail
98
- await meta.update('oidcRealm', (client as any)._realm)
99
- ```
192
+ ## A client id is a global name
193
+
194
+ A provider resolves a client from the bare `client_id` a relying party sends — the adapter gets no
195
+ tenant context — so the id is unique across the whole deployment, not per entity, and the store
196
+ cannot namespace it on a consumer's behalf. Uniqueness has to be *in the id*.
197
+
198
+ - Compose it with the owning entity in it. A name derived from a project's own name alone lets two
199
+ organizations share one registration: one secret, one redirect-URI list, one permission-definition
200
+ set, and one grant namespace, because `PermissionSet.scope` is that same string.
201
+ - `ensureClient` refuses a record belonging to another entity rather than returning it. The failure
202
+ is deliberate — a loud provisioning error is the only alternative to a silent cross-tenant handover.
203
+ - Assign an id **once** and reserve it with `claimClient`. Whatever a consumer derives ids from
204
+ (a project alias, a slug) may later be released and re-used by a sibling, so only the registry can
205
+ say whether an id is free. `deleteClient` gives it back, or a deleted project's name is burned.
206
+ - Backends whose clients are already per-tenant (keycloak realms) satisfy this by construction:
207
+ `claimClient` returns true and `deleteClient` may be `IamUnsupported`.
208
+
209
+ ## `IamClient.realm` field
210
+
211
+ `realm` is set by the provider to the entity id, so read `client.realm ?? fallbackEntityId` and never
212
+ reach into an adapter's own internals (`(client as any)._realm`) for the same value.
100
213
 
101
214
  ## Issuer & redirect URIs
102
215
 
@@ -117,39 +230,63 @@ config is self-sufficient too.
117
230
 
118
231
  **Always pass `redirectUris`.** Omitting them is a keycloak-only legacy default (`['*']`, which
119
232
  Keycloak expands per-origin). The integrated provider does exact `redirect_uri` matching and
120
- `oidc-provider` refuses to load a client whose `redirect_uris` are not absolute URIs — so
121
- `iam-integrated.ensureClient` throws `IamClientError('redirect-uris')` on creation rather than
233
+ `oidc-provider` refuses to load a client whose `redirect_uris` are not absolute URIs — so an
234
+ integrated backend's `ensureClient` throws `IamClientError('redirect-uris')` on creation rather than
122
235
  registering a client that can never complete a callback. An omitted list never widens an existing
123
236
  hardened client.
124
237
 
125
- ## Selecting the IAM provider
238
+ ## Selecting the IAM backend
126
239
 
127
- The platform wires the correct implementation via the `IAM_MODE` environment variable. The default is **`integrated`** (the internal IAM); `IAM_MODE=keycloak` is opt-in, used mainly for custom / standalone customer production setups. Do not read `IAM_MODE` directly from feature code — it belongs in the service-factory only (the platform exposes `resolveIamMode()` / `isIntegratedIam()` from `viable-common`):
240
+ The choice is made **once, in a service factory**, and nowhere else. Feature code depends on
241
+ `IamService` and never learns which implementation answers it. `IAM_MODE_INTEGRATED` /
242
+ `IAM_MODE_KEYCLOAK` and `OidcIamConfig.iamMode` (`@owlmeans/oidc`) exist so a deployment can carry
243
+ that choice as ordinary configuration rather than as a branch scattered through handlers. No OwlMeans
244
+ package reads `iamMode`; it is declared for the consumer's own factory to switch on, which is the
245
+ only thing that may branch.
128
246
 
129
247
  ```ts
130
- export const makeIamService = (mode = resolveIamMode()) => {
131
- if (mode === IAM_MODE_KEYCLOAK) return makeIamKeycloakService(VIABLE_IAM, { oidcProductAlias: OIDC_PRODUCT })
132
- // Pass the consumer's OWN service alias + base path: the package defaults ('manager-api',
133
- // '/oidc') are placeholders, and a mismatch makes every cfg.services[...] lookup miss.
134
- return makeIamIntegratedService(VIABLE_IAM, {
135
- providerServiceAlias: MANAGER_API, providerBasePath: IAM_PROVIDER_BASE_PATH,
136
- })
137
- }
248
+ import { DEFAULT_ALIAS, IAM_MODE_INTEGRATED, IAM_MODE_KEYCLOAK } from '@owlmeans/iam'
249
+ import type { IamMode, IamService } from '@owlmeans/iam'
250
+
251
+ // makeKeycloakAdapter / makeIntegratedAdapter are PLACEHOLDER names standing in for whichever
252
+ // adapter packages the deployment installs — this factory is the one place either is named, and
253
+ // every consumer resolves IamService by alias instead. An adapter exposes a
254
+ // `(alias?: string, options?: object) => IamService` factory whose alias defaults to DEFAULT_ALIAS.
255
+ export const makeIamService = (mode: IamMode = IAM_MODE_INTEGRATED): IamService =>
256
+ mode === IAM_MODE_KEYCLOAK
257
+ ? makeKeycloakAdapter(DEFAULT_ALIAS, /* adapter options */)
258
+ : makeIntegratedAdapter(DEFAULT_ALIAS, /* adapter options */)
259
+
260
+ // cfg.oidc typed as OidcSharedConfig & OidcIamConfig is what carries the mode
261
+ context.registerService(makeIamService(cfg.oidc?.iamMode))
138
262
  ```
139
263
 
140
- When a context hosts **more than one** `IamService` instance (the viable agent registers both
141
- `viable-backend`'s and the agent library's), every instance must be constructed with the same
142
- options — otherwise they disagree about the issuer.
264
+ An adapter that reassembles the issuer from a registered service alias plus a base path must be given
265
+ the **consumer's own** alias and path. Whatever an adapter defaults to is a placeholder, and a
266
+ mismatch makes every service lookup behind `getIssuerUrl` miss.
143
267
 
144
- ## Rules
268
+ An integrated backend — one built on `@owlmeans/server-oidc-provider`, so the deployment hosts its
269
+ own provider — is the default choice; a Keycloak-backed adapter exists for deployments that already
270
+ run one.
145
271
 
146
- - Always depend on `IamService` from `@owlmeans/iam`, not on `KeycloakApiService` directly.
147
- - Do not expose `getCredentialsPair` to request handlers — it returns a raw admin token.
148
- - `IamClientError` means the provisioned client was invalid (missing secret, null token). The proxy already throws it; callers only need to handle it, not re-throw wrapped errors.
149
- - The `realm` field on `IamClient` is guaranteed to be set by all concrete implementations.
272
+ When a context hosts **more than one** `IamService` instance, every instance must be constructed with
273
+ the same options — otherwise they disagree about the issuer, and only one of them can be right.
150
274
 
151
- ## Related instructions
275
+ ## Rules
152
276
 
153
- - `@owlmeans/iam-keycloak` (internal) — full proxy over Keycloak; see the `iam-keycloak` skill
154
- - `@owlmeans/keycloak` (internal) — low-level KC admin API; see the `keycloak` skill
155
- - `@owlmeans/server-iam` — IAM gate asserting both grant forms; see the `server-iam` skill
277
+ - Always depend on `IamService`, never on a backend's own admin client.
278
+ - Do not expose `getCredentialsPair` to request handlers — it returns a raw admin token.
279
+ - `IamClientError` means the provisioned client was invalid (missing secret, null token). The adapter
280
+ already throws it; callers handle it rather than re-throwing a wrapped error.
281
+ - `IamClient.realm` is set by every concrete implementation.
282
+ - An operation a backend cannot support throws `IamUnsupported('<what>')`. Callers turn that into a
283
+ fallback — an "external console" link, a disabled control — rather than an error screen.
284
+
285
+ ## Related
286
+
287
+ - `@owlmeans/server-iam` — the IAM gate that asserts both grant forms, and the gate-param grammar;
288
+ see the `server-iam` skill
289
+ - `@owlmeans/oidc` — provider descriptors and the `iamMode` config seam; see the `oidc` skill
290
+ - The concrete adapters (a Keycloak proxy, and the integrated backend built on
291
+ `@owlmeans/server-oidc-provider` plus `@owlmeans/server-auth-identity`) ship separately and are
292
+ reached only through `IamService`.
package/build/consts.d.ts CHANGED
@@ -2,4 +2,95 @@ export declare const DEFAULT_ALIAS = "iam-service";
2
2
  export declare const IAM_MODE_KEYCLOAK = "keycloak";
3
3
  export declare const IAM_MODE_INTEGRATED = "integrated";
4
4
  export type IamMode = typeof IAM_MODE_KEYCLOAK | typeof IAM_MODE_INTEGRATED;
5
+ /**
6
+ * Gate-param syntax: `<permission>[@<selector>]`.
7
+ *
8
+ * The `@` belongs to the GATE and to nothing else. It says where a resource id is read from at
9
+ * request time; it is never part of a permission's stored NAME. A definition or a grant whose name
10
+ * carries one is a key nothing ever looks up, so every grant against it is a silent no-op.
11
+ */
12
+ export declare const RESOURCE_PARAM_SEPARATOR = "@";
13
+ /**
14
+ * Separates an explicit source from its path: `@body:order.id`.
15
+ *
16
+ * Deliberately `:` rather than a dot. Under a dotted-only form the meaning of a selector changes the
17
+ * moment a route gains a param named like a source keyword — `/report/:query` with `@query.id` flips
18
+ * from "the route param literally named `query.id`" to "the query string's `id`", redirecting an
19
+ * authorization lookup from a router-matched value to attacker-supplied input, caused by an
20
+ * unrelated route rename. `:` is decidable in one character, and no gate-param string written before
21
+ * this existed can contain one.
22
+ */
23
+ export declare const RESOURCE_SOURCE_SEPARATOR = ":";
24
+ /** Separates path segments inside a qualified selector: `@body:order.id`. */
25
+ export declare const RESOURCE_PATH_SEPARATOR = ".";
26
+ /** Where a qualified selector may read a resource id from. */
27
+ export declare enum GateParamSource {
28
+ Params = "params",
29
+ Query = "query",
30
+ Body = "body",
31
+ Headers = "headers",
32
+ Auth = "auth"
33
+ }
34
+ /**
35
+ * What the bare form (`@enquiryId`) searches, in order.
36
+ *
37
+ * Params first, then query. The query fallback is legacy and deliberately kept: removing it would
38
+ * silently deny already-deployed applications whose selector happens to name a query key, and a
39
+ * silent denial inside a customer's running app is the worst available outcome.
40
+ */
41
+ export declare const DEFAULT_GATE_PARAM_SOURCES: GateParamSource[];
42
+ /** Why a gate param is structurally wrong — a misconfiguration, not a failed authorization. */
43
+ export declare enum GateParamErrorCode {
44
+ UnknownSource = "gate-param:unknown-source",
45
+ EmptySelector = "gate-param:empty-selector",
46
+ EmptySegment = "gate-param:empty-segment",
47
+ /** Named a request key the entrypoint's filter does not declare — AJV strips it before the gate. */
48
+ UnreachableKey = "gate-param:unreachable-key",
49
+ /** Named a route param the entrypoint's own path does not carry. */
50
+ UnknownRouteParam = "gate-param:unknown-route-param"
51
+ }
52
+ /** Why a selector produced no resource id. */
53
+ export declare enum GateResolutionFailure {
54
+ /** The source exists, the path does not. */
55
+ NotProvided = "not-provided",
56
+ /** The request carries no such source at all. */
57
+ SourceMissing = "source-missing",
58
+ /** Resolved to an object or a multi-element array — refused rather than stringified. */
59
+ NotScalar = "not-scalar",
60
+ /** The selector itself did not parse. */
61
+ Malformed = "malformed"
62
+ }
63
+ /**
64
+ * Which FORM of a grant an operation addresses.
65
+ *
66
+ * A resource-scoped permission is grantable two ways, under one name: `Blanket` (a PermissionSet
67
+ * carrying no `resources`, which `hasPermission` reads as covering every id) and `Resources` (an
68
+ * explicit id list). They are stored separately and must be revoked separately, or a UI offering
69
+ * both controls would have one silently wipe the other.
70
+ */
71
+ export declare enum IamGrantMode {
72
+ Blanket = "blanket",
73
+ Resources = "resources",
74
+ /** Revoke-only: every form. Illegal on a grant. */
75
+ All = "all"
76
+ }
77
+ /** What to do about grants that still reference a definition being removed. */
78
+ export declare enum IamRemovalPolicy {
79
+ /** Strip the permission from every subject of the entity, then drop the definition. */
80
+ Cascade = "cascade",
81
+ /** Refuse while any subject still holds it; the holders travel in the error. */
82
+ Refuse = "refuse"
83
+ }
84
+ /**
85
+ * The grouping tiers a permission definition may be tagged with.
86
+ *
87
+ * A tag decides NOTHING at request time — `hasPermission` and every gate treat a permission name as
88
+ * one opaque string — and only groups the permission in an administrator's screen. `guest` is
89
+ * deliberately absent: a public surface declares no permissions, and adding an area later would
90
+ * re-classify names already tagged.
91
+ */
92
+ export declare const IAM_AREAS: readonly ['user', 'operator', 'admin'];
93
+ export type IamArea = typeof IAM_AREAS[number];
94
+ /** Separates the resource from the action in a permission name. TWO hyphens, never one. */
95
+ export declare const PERMISSION_ACTION_SEPARATOR = "--";
5
96
  //# sourceMappingURL=consts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,gBAAgB,CAAA;AAE1C,eAAO,MAAM,iBAAiB,aAAa,CAAA;AAC3C,eAAO,MAAM,mBAAmB,eAAe,CAAA;AAE/C,MAAM,MAAM,OAAO,GAAG,OAAO,iBAAiB,GAAG,OAAO,mBAAmB,CAAA"}
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,gBAAgB,CAAA;AAE1C,eAAO,MAAM,iBAAiB,aAAa,CAAA;AAC3C,eAAO,MAAM,mBAAmB,eAAe,CAAA;AAE/C,MAAM,MAAM,OAAO,GAAG,OAAO,iBAAiB,GAAG,OAAO,mBAAmB,CAAA;AAE3E;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAA;AAE3C;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAE5C,6EAA6E;AAC7E,eAAO,MAAM,uBAAuB,MAAM,CAAA;AAE1C,8DAA8D;AAC9D,oBAAY,eAAe;IACzB,MAAM,WAAW;IACjB,KAAK,UAAU;IACf,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,IAAI,SAAS;CACd;AAED;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,EAAE,eAAe,EAEvD,CAAA;AAED,+FAA+F;AAC/F,oBAAY,kBAAkB;IAC5B,aAAa,8BAA8B;IAC3C,aAAa,8BAA8B;IAC3C,YAAY,6BAA6B;IACzC,oGAAoG;IACpG,cAAc,+BAA+B;IAC7C,oEAAoE;IACpE,iBAAiB,mCAAmC;CACrD;AAED,8CAA8C;AAC9C,oBAAY,qBAAqB;IAC/B,4CAA4C;IAC5C,WAAW,iBAAiB;IAC5B,iDAAiD;IACjD,aAAa,mBAAmB;IAChC,wFAAwF;IACxF,SAAS,eAAe;IACxB,yCAAyC;IACzC,SAAS,cAAc;CACxB;AAED;;;;;;;GAOG;AACH,oBAAY,YAAY;IACtB,OAAO,YAAY;IACnB,SAAS,cAAc;IACvB,mDAAmD;IACnD,GAAG,QAAQ;CACZ;AAED,+EAA+E;AAC/E,oBAAY,gBAAgB;IAC1B,uFAAuF;IACvF,OAAO,YAAY;IACnB,gFAAgF;IAChF,MAAM,WAAW;CAClB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,YAAI,MAAM,EAAE,UAAU,EAAE,OAAO,CAAU,CAAA;AAE/D,MAAM,MAAM,OAAO,GAAG,OAAO,SAAS,CAAC,MAAM,CAAC,CAAA;AAE9C,2FAA2F;AAC3F,eAAO,MAAM,2BAA2B,OAAO,CAAA"}
package/build/consts.js CHANGED
@@ -1,4 +1,101 @@
1
1
  export const DEFAULT_ALIAS = 'iam-service';
2
2
  export const IAM_MODE_KEYCLOAK = 'keycloak';
3
3
  export const IAM_MODE_INTEGRATED = 'integrated';
4
+ /**
5
+ * Gate-param syntax: `<permission>[@<selector>]`.
6
+ *
7
+ * The `@` belongs to the GATE and to nothing else. It says where a resource id is read from at
8
+ * request time; it is never part of a permission's stored NAME. A definition or a grant whose name
9
+ * carries one is a key nothing ever looks up, so every grant against it is a silent no-op.
10
+ */
11
+ export const RESOURCE_PARAM_SEPARATOR = '@';
12
+ /**
13
+ * Separates an explicit source from its path: `@body:order.id`.
14
+ *
15
+ * Deliberately `:` rather than a dot. Under a dotted-only form the meaning of a selector changes the
16
+ * moment a route gains a param named like a source keyword — `/report/:query` with `@query.id` flips
17
+ * from "the route param literally named `query.id`" to "the query string's `id`", redirecting an
18
+ * authorization lookup from a router-matched value to attacker-supplied input, caused by an
19
+ * unrelated route rename. `:` is decidable in one character, and no gate-param string written before
20
+ * this existed can contain one.
21
+ */
22
+ export const RESOURCE_SOURCE_SEPARATOR = ':';
23
+ /** Separates path segments inside a qualified selector: `@body:order.id`. */
24
+ export const RESOURCE_PATH_SEPARATOR = '.';
25
+ /** Where a qualified selector may read a resource id from. */
26
+ export var GateParamSource;
27
+ (function (GateParamSource) {
28
+ GateParamSource["Params"] = "params";
29
+ GateParamSource["Query"] = "query";
30
+ GateParamSource["Body"] = "body";
31
+ GateParamSource["Headers"] = "headers";
32
+ GateParamSource["Auth"] = "auth";
33
+ })(GateParamSource || (GateParamSource = {}));
34
+ /**
35
+ * What the bare form (`@enquiryId`) searches, in order.
36
+ *
37
+ * Params first, then query. The query fallback is legacy and deliberately kept: removing it would
38
+ * silently deny already-deployed applications whose selector happens to name a query key, and a
39
+ * silent denial inside a customer's running app is the worst available outcome.
40
+ */
41
+ export const DEFAULT_GATE_PARAM_SOURCES = [
42
+ GateParamSource.Params, GateParamSource.Query
43
+ ];
44
+ /** Why a gate param is structurally wrong — a misconfiguration, not a failed authorization. */
45
+ export var GateParamErrorCode;
46
+ (function (GateParamErrorCode) {
47
+ GateParamErrorCode["UnknownSource"] = "gate-param:unknown-source";
48
+ GateParamErrorCode["EmptySelector"] = "gate-param:empty-selector";
49
+ GateParamErrorCode["EmptySegment"] = "gate-param:empty-segment";
50
+ /** Named a request key the entrypoint's filter does not declare — AJV strips it before the gate. */
51
+ GateParamErrorCode["UnreachableKey"] = "gate-param:unreachable-key";
52
+ /** Named a route param the entrypoint's own path does not carry. */
53
+ GateParamErrorCode["UnknownRouteParam"] = "gate-param:unknown-route-param";
54
+ })(GateParamErrorCode || (GateParamErrorCode = {}));
55
+ /** Why a selector produced no resource id. */
56
+ export var GateResolutionFailure;
57
+ (function (GateResolutionFailure) {
58
+ /** The source exists, the path does not. */
59
+ GateResolutionFailure["NotProvided"] = "not-provided";
60
+ /** The request carries no such source at all. */
61
+ GateResolutionFailure["SourceMissing"] = "source-missing";
62
+ /** Resolved to an object or a multi-element array — refused rather than stringified. */
63
+ GateResolutionFailure["NotScalar"] = "not-scalar";
64
+ /** The selector itself did not parse. */
65
+ GateResolutionFailure["Malformed"] = "malformed";
66
+ })(GateResolutionFailure || (GateResolutionFailure = {}));
67
+ /**
68
+ * Which FORM of a grant an operation addresses.
69
+ *
70
+ * A resource-scoped permission is grantable two ways, under one name: `Blanket` (a PermissionSet
71
+ * carrying no `resources`, which `hasPermission` reads as covering every id) and `Resources` (an
72
+ * explicit id list). They are stored separately and must be revoked separately, or a UI offering
73
+ * both controls would have one silently wipe the other.
74
+ */
75
+ export var IamGrantMode;
76
+ (function (IamGrantMode) {
77
+ IamGrantMode["Blanket"] = "blanket";
78
+ IamGrantMode["Resources"] = "resources";
79
+ /** Revoke-only: every form. Illegal on a grant. */
80
+ IamGrantMode["All"] = "all";
81
+ })(IamGrantMode || (IamGrantMode = {}));
82
+ /** What to do about grants that still reference a definition being removed. */
83
+ export var IamRemovalPolicy;
84
+ (function (IamRemovalPolicy) {
85
+ /** Strip the permission from every subject of the entity, then drop the definition. */
86
+ IamRemovalPolicy["Cascade"] = "cascade";
87
+ /** Refuse while any subject still holds it; the holders travel in the error. */
88
+ IamRemovalPolicy["Refuse"] = "refuse";
89
+ })(IamRemovalPolicy || (IamRemovalPolicy = {}));
90
+ /**
91
+ * The grouping tiers a permission definition may be tagged with.
92
+ *
93
+ * A tag decides NOTHING at request time — `hasPermission` and every gate treat a permission name as
94
+ * one opaque string — and only groups the permission in an administrator's screen. `guest` is
95
+ * deliberately absent: a public surface declares no permissions, and adding an area later would
96
+ * re-classify names already tagged.
97
+ */
98
+ export const IAM_AREAS = ['user', 'operator', 'admin'];
99
+ /** Separates the resource from the action in a permission name. TWO hyphens, never one. */
100
+ export const PERMISSION_ACTION_SEPARATOR = '--';
4
101
  //# sourceMappingURL=consts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAA;AAE1C,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAAA;AAC3C,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAAA"}
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAA;AAE1C,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAAA;AAC3C,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAAA;AAI/C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAA;AAE3C;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAA;AAE5C,6EAA6E;AAC7E,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAA;AAE1C,8DAA8D;AAC9D,MAAM,CAAN,IAAY,eAMX;AAND,WAAY,eAAe;IACzB,oCAAiB,CAAA;IACjB,kCAAe,CAAA;IACf,gCAAa,CAAA;IACb,sCAAmB,CAAA;IACnB,gCAAa,CAAA;AACf,CAAC,EANW,eAAe,KAAf,eAAe,QAM1B;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAsB;IAC3D,eAAe,CAAC,MAAM,EAAE,eAAe,CAAC,KAAK;CAC9C,CAAA;AAED,+FAA+F;AAC/F,MAAM,CAAN,IAAY,kBAQX;AARD,WAAY,kBAAkB;IAC5B,iEAA2C,CAAA;IAC3C,iEAA2C,CAAA;IAC3C,+DAAyC,CAAA;IACzC,oGAAoG;IACpG,mEAA6C,CAAA;IAC7C,oEAAoE;IACpE,0EAAoD,CAAA;AACtD,CAAC,EARW,kBAAkB,KAAlB,kBAAkB,QAQ7B;AAED,8CAA8C;AAC9C,MAAM,CAAN,IAAY,qBASX;AATD,WAAY,qBAAqB;IAC/B,4CAA4C;IAC5C,qDAA4B,CAAA;IAC5B,iDAAiD;IACjD,yDAAgC,CAAA;IAChC,wFAAwF;IACxF,iDAAwB,CAAA;IACxB,yCAAyC;IACzC,gDAAuB,CAAA;AACzB,CAAC,EATW,qBAAqB,KAArB,qBAAqB,QAShC;AAED;;;;;;;GAOG;AACH,MAAM,CAAN,IAAY,YAKX;AALD,WAAY,YAAY;IACtB,mCAAmB,CAAA;IACnB,uCAAuB,CAAA;IACvB,mDAAmD;IACnD,2BAAW,CAAA;AACb,CAAC,EALW,YAAY,KAAZ,YAAY,QAKvB;AAED,+EAA+E;AAC/E,MAAM,CAAN,IAAY,gBAKX;AALD,WAAY,gBAAgB;IAC1B,uFAAuF;IACvF,uCAAmB,CAAA;IACnB,gFAAgF;IAChF,qCAAiB,CAAA;AACnB,CAAC,EALW,gBAAgB,KAAhB,gBAAgB,QAK3B;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAU,CAAA;AAI/D,2FAA2F;AAC3F,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAA"}
package/build/errors.d.ts CHANGED
@@ -15,6 +15,19 @@ export declare class IamGrantError extends IamError {
15
15
  static typeName: string;
16
16
  constructor(message?: string);
17
17
  }
18
+ /**
19
+ * A refusal about a permission DEFINITION, as opposed to a grant.
20
+ *
21
+ * Separate from `IamGrantError`, which means a grant's subject is missing or its entity does not
22
+ * match — reusing it for a definition-level refusal is a category error the next reader has to unpick.
23
+ *
24
+ * Codes: `permission:held:<name>` (still granted to someone, under a policy that refuses),
25
+ * `permission:managed:<name>` (platform-owned, deleted only with an explicit second key).
26
+ */
27
+ export declare class IamPermissionError extends IamError {
28
+ static typeName: string;
29
+ constructor(message?: string);
30
+ }
18
31
  export declare class IamUnsupported extends IamError {
19
32
  static typeName: string;
20
33
  constructor(message?: string);
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,qBAAa,QAAS,SAAQ,cAAc;IAC1C,OAAuB,QAAQ,SAAkC;IAEjE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,OAAuB,QAAQ,SAAwC;IAEvE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,gBAAiB,SAAQ,QAAQ;IAC5C,OAAuB,QAAQ,SAA0C;IAEzE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,aAAc,SAAQ,QAAQ;IACzC,OAAuB,QAAQ,SAAuC;IAEtE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,OAAuB,QAAQ,SAA6C;IAE5E,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,YAAa,SAAQ,QAAQ;IACxC,OAAuB,QAAQ,SAAsC;IAErE,YAAY,OAAO,SAAU,EAG5B;CACF"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,qBAAa,QAAS,SAAQ,cAAc;IAC1C,OAAuB,QAAQ,SAAkC;IAEjE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,OAAuB,QAAQ,SAAwC;IAEvE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,gBAAiB,SAAQ,QAAQ;IAC5C,OAAuB,QAAQ,SAA0C;IAEzE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,aAAc,SAAQ,QAAQ;IACzC,OAAuB,QAAQ,SAAuC;IAEtE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED;;;;;;;;GAQG;AACH,qBAAa,kBAAmB,SAAQ,QAAQ;IAC9C,OAAuB,QAAQ,SAA4C;IAE3E,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,OAAuB,QAAQ,SAA6C;IAE5E,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,YAAa,SAAQ,QAAQ;IACxC,OAAuB,QAAQ,SAAsC;IAErE,YAAY,OAAO,SAAU,EAG5B;CACF"}