@voltro/cli 0.30.1 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +163 -0
- package/dist/apiBuild-BOqL1dlg.js +2 -0
- package/dist/{apiBuild-DmkzRyv7.js → apiBuild-CLPt-pX6.js} +2 -2
- package/dist/appGraph-CvQCte0z.js +2 -0
- package/dist/appGraph-gQ_6GkQQ.js +110 -0
- package/dist/bin.js +3 -3
- package/dist/checkCommand-ClRvH_SN.js +2 -0
- package/dist/{checkCommand-C4konUy9.js → checkCommand-D7r6_pk7.js} +439 -530
- package/dist/{commands-BkJft9K2.js → commands-CNVVv03h.js} +1941 -1880
- package/dist/{dbCommand-qn__kLEt.js → dbCommand-BH8GAYuf.js} +76 -19
- package/dist/dbCommand-BwQ_Jl98.js +2 -0
- package/dist/{dev-DXhhZ7-f.js → dev-DOZVMVfi.js} +1 -1
- package/dist/{dev-C_NPcLDp.js → dev-DdQEn2Ia.js} +1986 -1967
- package/dist/{frameworkTableAssembly-Cpgn2e88.js → frameworkTableAssembly-BwIrO5nv.js} +122 -103
- package/dist/frameworkTableAssembly-D-EebUQX.js +2 -0
- package/dist/index.js +1 -1
- package/dist/{inspectMetrics-CkB2J0Zn.js → inspectMetrics-BeEAnjqw.js} +774 -527
- package/dist/{sdkgen-wsWLrq4l.js → sdkgen-v9WV8zRi.js} +1 -1
- package/dist/{serveCommand-HtI4X5kj.js → serveCommand-KsKXXWn4.js} +294 -293
- package/dist/serveEntry.js +2 -2
- package/dist/{start-egkbpugc.js → start-CI2QdKy9.js} +351 -346
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.md +43 -43
- package/templates/agent-docs/_index.md +43 -43
- package/templates/agent-docs/_manifest.json +84 -42
- package/templates/agent-docs/cli.md +4 -2
- package/templates/agent-docs/database/migrations.md +14 -1
- package/templates/agent-docs/internationalization.md +54 -3
- package/templates/agent-docs/plugins/ai-flows.md +148 -0
- package/templates/agent-docs/plugins/analytics-postgres.md +90 -0
- package/templates/agent-docs/plugins/atlassian.md +378 -0
- package/templates/agent-docs/plugins/audit.md +324 -0
- package/templates/agent-docs/plugins/auth-auth0.md +92 -0
- package/templates/agent-docs/plugins/auth-clerk.md +90 -0
- package/templates/agent-docs/plugins/auth-kinde.md +88 -0
- package/templates/agent-docs/plugins/auth-oidc.md +95 -0
- package/templates/agent-docs/plugins/auth-supabase.md +103 -0
- package/templates/agent-docs/plugins/auth-workos.md +105 -0
- package/templates/agent-docs/plugins/auth.md +227 -0
- package/templates/agent-docs/plugins/billing.md +324 -0
- package/templates/agent-docs/plugins/broadcast.md +144 -0
- package/templates/agent-docs/plugins/cdc-out.md +116 -0
- package/templates/agent-docs/plugins/clickhouse.md +91 -0
- package/templates/agent-docs/plugins/datadog.md +105 -0
- package/templates/agent-docs/plugins/deactivation.md +114 -0
- package/templates/agent-docs/plugins/duckdb.md +82 -0
- package/templates/agent-docs/plugins/flags.md +146 -0
- package/templates/agent-docs/plugins/governance.md +143 -0
- package/templates/agent-docs/plugins/licensing.md +74 -0
- package/templates/agent-docs/plugins/logship.md +56 -0
- package/templates/agent-docs/plugins/mail.md +334 -0
- package/templates/agent-docs/plugins/moderation.md +65 -0
- package/templates/agent-docs/plugins/multitenancy.md +99 -0
- package/templates/agent-docs/plugins/notifications.md +252 -0
- package/templates/agent-docs/plugins/openapi.md +94 -0
- package/templates/agent-docs/plugins/postgis.md +140 -0
- package/templates/agent-docs/plugins/posthog.md +91 -0
- package/templates/agent-docs/plugins/presence.md +115 -0
- package/templates/agent-docs/plugins/prometheus.md +124 -0
- package/templates/agent-docs/plugins/ratelimit.md +274 -0
- package/templates/agent-docs/plugins/rbac.md +270 -0
- package/templates/agent-docs/plugins/scim.md +63 -0
- package/templates/agent-docs/plugins/search.md +116 -0
- package/templates/agent-docs/plugins/sentry.md +192 -0
- package/templates/agent-docs/plugins/soft-delete.md +72 -0
- package/templates/agent-docs/plugins/sso-saml.md +132 -0
- package/templates/agent-docs/plugins/storage.md +709 -0
- package/templates/agent-docs/plugins/tinybird.md +92 -0
- package/templates/agent-docs/plugins/versioning.md +226 -0
- package/templates/agent-docs/plugins/webhooks.md +585 -0
- package/templates/agent-docs/security.md +12 -1
- package/templates/agent-docs/whats-new.md +81 -37
- package/templates/apps/api-ai/package.json +7 -7
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +10 -10
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-collab/package.json +9 -9
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-portal/package.json +8 -8
- package/templates/apps/frontend-saas/package.json +8 -8
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/package.json +2 -2
- package/dist/apiBuild-Ca6Sq5jy.js +0 -2
- package/dist/checkCommand-DHD0aBeR.js +0 -2
- package/dist/dbCommand-wfGi3FgG.js +0 -2
- package/dist/frameworkTableAssembly-Bn_c4Mnl.js +0 -2
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Soft delete
|
|
2
|
+
|
|
3
|
+
> The softDelete() schema mixin — deletedAt / deletedBy columns, delete() redirected to an UPDATE, default reads filtered, hardDelete() bypass.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<!-- source: en/plugins/soft-delete.md -->
|
|
10
|
+
## Soft delete
|
|
11
|
+
|
|
12
|
+
_The softDelete() schema mixin — deletedAt / deletedBy columns, delete() redirected to an UPDATE, default reads filtered, hardDelete() bypass._
|
|
13
|
+
|
|
14
|
+
`@voltro/plugin-soft-delete` ships a single surface: the `softDelete()` schema mixin. A "deleted" row stays in the database but disappears from default reads — the audit trail is preserved and the delete is reversible.
|
|
15
|
+
|
|
16
|
+
**Status:** ✓ shipped.
|
|
17
|
+
|
|
18
|
+
## The `softDelete()` mixin
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { table, id, text } from '@voltro/database'
|
|
22
|
+
import { softDelete } from '@voltro/plugin-soft-delete'
|
|
23
|
+
|
|
24
|
+
export const todos = table('todos', {
|
|
25
|
+
id: id(),
|
|
26
|
+
title: text(),
|
|
27
|
+
}).with(softDelete())
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
It adds two columns:
|
|
31
|
+
|
|
32
|
+
- `deletedAt` — `timestamp().nullable()`
|
|
33
|
+
- `deletedBy` — `reference()` to the `actors` table, nullable
|
|
34
|
+
|
|
35
|
+
`softDelete()` transitively requires `audit()`, so composing it also pulls in the `createdAt` / `updatedAt` / `createdBy` / `updatedBy` columns. The mixin is also available on the `./mixin` subpath (`@voltro/plugin-soft-delete/mixin`).
|
|
36
|
+
|
|
37
|
+
## Runtime behaviour
|
|
38
|
+
|
|
39
|
+
The mixin's stable id (`voltro/softDelete`) drives two automatic behaviours in the runtime:
|
|
40
|
+
|
|
41
|
+
- **`ctx.store.delete(table, pk)` becomes an UPDATE.** On a softDelete-mixed table, a delete fills `deletedAt` + `deletedBy` (from `ctx.actor`) instead of removing the row.
|
|
42
|
+
- **Default reads filter `deletedAt IS NULL`.** The mixin's `defaultWhere` documents the intent; the runtime wires the actual filter at subscribe time, so soft-deleted rows are invisible to normal queries without any per-query effort.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// Soft-delete — row stays, just hidden from default reads.
|
|
46
|
+
await ctx.store.delete('todos', todoId)
|
|
47
|
+
|
|
48
|
+
// Hard-delete escape hatch — actually removes the row, bypassing the mixin.
|
|
49
|
+
await ctx.store.hardDelete('todos', todoId)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Use `hardDelete()` sparingly — the whole point of soft-delete is preserving the audit trail.
|
|
53
|
+
|
|
54
|
+
## Reading + restoring deleted rows
|
|
55
|
+
|
|
56
|
+
Default reads hide soft-deleted rows. Opt out with `.withDeleted()` to build a trash view, and `restore()` to undelete:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { restore } from '@voltro/plugin-soft-delete'
|
|
60
|
+
|
|
61
|
+
// List tombstones (deleted + live) for a restore UI.
|
|
62
|
+
const rows = await ctx.store.select('todos').withDeleted().all()
|
|
63
|
+
|
|
64
|
+
// Undelete — clears deletedAt + deletedBy by primary key; the row reappears.
|
|
65
|
+
await restore(ctx.store, 'todos', todoId)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`restore()` patches by primary key, so it works even though default reads hide the row (you only need `.withDeleted()` to *find* what to restore). It's idempotent. There's no runtime "undelete" redirect — restore is a plain patch of the two columns, wrapped so a trash UI doesn't hand-roll it.
|
|
69
|
+
|
|
70
|
+
## Soft-delete vs deactivation
|
|
71
|
+
|
|
72
|
+
`softDelete()` **hides** a row from default reads (and is the GDPR-anonymisation path). [`deactivation()`](/docs/plugins/deactivation) is the deliberate opposite: it marks a subject as unable to log in while keeping its data fully **visible**. Compose `.with(audit(), softDelete(), deactivation())` for the full user-lifecycle column set.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# SAML SSO
|
|
2
|
+
|
|
3
|
+
> Enterprise SAML 2.0 SSO — SP-initiated login, ACS assertion consumer, SP metadata. Signature verification via @node-saml/node-saml; framework session minting built in.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<!-- source: en/plugins/sso-saml.md -->
|
|
10
|
+
## SAML SSO
|
|
11
|
+
|
|
12
|
+
_Enterprise SAML 2.0 SSO — SP-initiated login, ACS assertion consumer, SP metadata. Signature verification via @node-saml/node-saml; framework session minting built in._
|
|
13
|
+
|
|
14
|
+
`@voltro/plugin-sso-saml` adds SAML 2.0 single sign-on with **Single Logout (SLO)** — the login protocol large enterprises mandate (alongside, or instead of, OIDC). The framework's six IdP adapters cover JWT/OIDC; this covers **SAML**. Signature verification is delegated to the maintained `@node-saml/node-saml` (an optional, lazily-loaded dependency — install it to use the plugin); the framework integration (routes, session minting, attribute mapping, metadata) is built in. Beyond the base flow it supports IdP-metadata-URL config (auto cert rotation), encrypted assertions, a clock-skew tolerance, and SP request signing.
|
|
15
|
+
|
|
16
|
+
## Wiring
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
// app.config.ts
|
|
20
|
+
import { samlSsoPlugin } from '@voltro/plugin-sso-saml'
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
type: 'api' as const, name: 'api',
|
|
24
|
+
plugins: [
|
|
25
|
+
samlSsoPlugin({
|
|
26
|
+
idp: { entryPoint: process.env.SAML_IDP_SSO_URL!, idpCert: process.env.SAML_IDP_CERT! },
|
|
27
|
+
sp: { entityId: 'https://app.example.com/saml/metadata', acsUrl: 'https://app.example.com/saml/acs' },
|
|
28
|
+
sessionSecret: process.env.VOLTRO_SESSION_SECRET!,
|
|
29
|
+
// Map the validated SAML identity → an app Subject (look up / JIT-create a user).
|
|
30
|
+
onLogin: async (profile) => {
|
|
31
|
+
const user = await findOrCreateUser(profile.email ?? profile.nameId)
|
|
32
|
+
return { type: 'user', id: user.id, tenantId: user.orgId }
|
|
33
|
+
},
|
|
34
|
+
successRedirect: '/',
|
|
35
|
+
}),
|
|
36
|
+
],
|
|
37
|
+
}
|
|
38
|
+
// install once: pnpm add @node-saml/node-saml
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Endpoints (mounted under `/saml`)
|
|
42
|
+
|
|
43
|
+
- **`GET /saml/metadata`** — SP metadata XML to paste into your IdP (tells it your ACS URL + entity id, the SP SLO endpoint, and — with `signingCert` — the SP signing cert). Works without the optional dep.
|
|
44
|
+
- **`GET /saml/login?returnTo=/dashboard`** — builds an AuthnRequest and redirects the browser to the IdP. Unsigned by default; set `privateKey` to sign it (the SP metadata then declares `AuthnRequestsSigned="true"`).
|
|
45
|
+
- **`POST /saml/acs`** — the Assertion Consumer Service: validates the IdP's signed assertion, maps it via `onLogin`, mints an HttpOnly framework session cookie (`signSession`), persists the NameID/SessionIndex for logout, and redirects to `returnTo` / `successRedirect`.
|
|
46
|
+
- **`GET /saml/logout`** — SP-initiated Single Logout: builds a `LogoutRequest` (signed when `privateKey` is set) referencing the stored NameID + SessionIndex, clears the session, and redirects to the IdP SLO endpoint.
|
|
47
|
+
- **`GET|POST /saml/slo`** — the IdP-facing SLO endpoint: an IdP-initiated `LogoutRequest` (signature verified) or the `LogoutResponse` to our own request lands here.
|
|
48
|
+
|
|
49
|
+
The flow: user hits `/saml/login` → IdP authenticates → IdP POSTs the assertion to `/saml/acs` → verified → session cookie set → user is logged in (the next WS connection resolves the authenticated Subject).
|
|
50
|
+
|
|
51
|
+
## Single Logout (SLO)
|
|
52
|
+
|
|
53
|
+
SLO works in **both directions**. A `LogoutRequest` must reference the IdP `NameID` + `SessionIndex` from the login, so those are captured at the ACS and stored keyed by an opaque HttpOnly companion cookie.
|
|
54
|
+
|
|
55
|
+
- **SP-initiated** (`GET /saml/logout`) — loads the stored NameID/SessionIndex, builds the (signed) `LogoutRequest`, clears the session cookie, and redirects to the IdP SLO endpoint.
|
|
56
|
+
- **IdP-initiated** (`GET|POST /saml/slo`) — the IdP's signed `LogoutRequest` clears the SP session and is answered with a `LogoutResponse`; a bad signature is rejected (`401`) and the session is left intact.
|
|
57
|
+
|
|
58
|
+
The SLO state store mirrors the replay cache — in-process by default (single replica), shared via the DataStore with `sloStore: { store: true }` (a `_voltro_saml_logout` table) so a login on one replica can log out on another.
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
samlSsoPlugin({
|
|
62
|
+
// …idp / sp / sessionSecret / onLogin…
|
|
63
|
+
sloStore: { store: true }, // shared across replicas
|
|
64
|
+
logoutRedirect: '/goodbye',
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## IdP-metadata-URL config (auto cert rotation)
|
|
69
|
+
|
|
70
|
+
Instead of pinning `idp.entryPoint` + `idp.idpCert`, point the plugin at the IdP metadata document. It fetches the XML (via `@effect/platform`'s `HttpClient`), reads the SSO `entryPoint`, the **signing** certificate, and the `SingleLogoutService`, and re-fetches on the `metadataRefreshMs` schedule — so when the IdP rotates its cert you change nothing.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
samlSsoPlugin({
|
|
74
|
+
idp: {
|
|
75
|
+
metadataUrl: process.env.SAML_IDP_METADATA_URL!, // entryPoint + cert loaded from here
|
|
76
|
+
metadataRefreshMs: 6 * 60 * 60 * 1000, // re-fetch every 6h (0 disables)
|
|
77
|
+
},
|
|
78
|
+
sp: { entityId: 'https://app.example.com/saml/metadata', acsUrl: 'https://app.example.com/saml/acs' },
|
|
79
|
+
sessionSecret: process.env.VOLTRO_SESSION_SECRET!,
|
|
80
|
+
onLogin: (profile) => lookupOrCreateUser(profile),
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
An explicit `entryPoint` / `idpCert` still act as a fallback if a fetch fails, so a transient metadata outage never takes down login.
|
|
85
|
+
|
|
86
|
+
## Encrypted assertions, clock skew, SP signing
|
|
87
|
+
|
|
88
|
+
- **Encrypted assertions** — `decryptionPvk` is the SP private key node-saml uses to decrypt an `EncryptedAssertion`. Encrypted and plaintext assertions both work.
|
|
89
|
+
- **Clock skew** — `acceptedClockSkewMs` widens the tolerated window for the assertion's `NotBefore` / `NotOnOrAfter` timestamps.
|
|
90
|
+
- **SP request signing** — `privateKey` + `signingCert` sign the `AuthnRequest` / `LogoutRequest`; the SP metadata advertises `AuthnRequestsSigned="true"` and publishes the signing cert.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
samlSsoPlugin({
|
|
94
|
+
idp: { entryPoint: process.env.SAML_IDP_SSO_URL!, idpCert: process.env.SAML_IDP_CERT! },
|
|
95
|
+
sp: { entityId: 'https://app.example.com/saml/metadata', acsUrl: 'https://app.example.com/saml/acs' },
|
|
96
|
+
sessionSecret: process.env.VOLTRO_SESSION_SECRET!,
|
|
97
|
+
onLogin: (profile) => lookupOrCreateUser(profile),
|
|
98
|
+
decryptionPvk: process.env.SAML_SP_DECRYPTION_KEY!, // decrypt EncryptedAssertion
|
|
99
|
+
privateKey: process.env.SAML_SP_SIGNING_KEY!, // sign AuthnRequest / LogoutRequest
|
|
100
|
+
signingCert: process.env.SAML_SP_SIGNING_CERT!, // published in SP metadata
|
|
101
|
+
acceptedClockSkewMs: 5000, // 5s clock-skew tolerance
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
All three secrets come from env / a secrets backend — never a literal, never logged.
|
|
106
|
+
|
|
107
|
+
## Replay / `InResponseTo` protection
|
|
108
|
+
|
|
109
|
+
By default node-saml runs with `validateInResponseTo: 'never'`, so a captured `SAMLResponse` can be replayed to the ACS within its validity window (and unsolicited IdP-initiated posts are accepted). Turn on the one-time request-id cache with `replayProtection`:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
samlSsoPlugin({
|
|
113
|
+
// …idp / sp / sessionSecret / onLogin…
|
|
114
|
+
replayProtection: { store: true }, // shared across replicas — the production choice
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
- `false` (default) — off. Single-replica dev / PoC only.
|
|
119
|
+
- `true` — on with an **in-process** cache. Correct for one replica only; a login and its ACS POST that land on different processes fail the check (boot warns).
|
|
120
|
+
- `{ store: true, ttlMs? }` — on, backed by the framework DataStore (a contributed `_voltro_saml_replay` table). Shared across replicas. Declares `store:write`. `ttlMs` bounds an outstanding request's validity (default 10 min).
|
|
121
|
+
|
|
122
|
+
The `AuthnRequest` id is stored at `/saml/login` and **consumed** at `/saml/acs`, so the same assertion can't be replayed and a response referencing no request this SP issued is rejected.
|
|
123
|
+
|
|
124
|
+
## Notes
|
|
125
|
+
|
|
126
|
+
- `WantAssertionsSigned` is on — the plugin rejects unsigned assertions.
|
|
127
|
+
- The session cookie is the same one [`@voltro/plugin-auth`](/docs/plugins/auth) / `verifySession` read, so the rest of your app authenticates identically.
|
|
128
|
+
- `idpCert` is the IdP's signing certificate (PEM body) — get it from the IdP's metadata.
|
|
129
|
+
|
|
130
|
+
## Permissions
|
|
131
|
+
|
|
132
|
+
None by default (it serves the SAML routes + mints a session). `replayProtection: { store: true }` and `sloStore: { store: true }` each declare `store:write` for their shared table (`_voltro_saml_replay` / `_voltro_saml_logout`).
|