@bonnard/cli 0.2.12 → 0.2.14

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.
@@ -68,6 +68,7 @@
68
68
  ## Other
69
69
 
70
70
  - [governance](governance) - User and group-level permissions
71
+ - [security-context](security-context) - B2B multi-tenancy with security context
71
72
  - [catalog](catalog) - Browse your data model in the browser
72
73
  - [slack-teams](slack-teams) - AI agents in team chat (coming soon)
73
74
 
@@ -23,7 +23,7 @@ Choose the component that best fits your data:
23
23
 
24
24
  - Components are self-closing (`/>`)
25
25
  - `data` uses curly braces: `data={query_name}`
26
- - Other props use quotes: `x="field_name"`
26
+ - Other props use quotes: `x="orders.city"`
27
27
  - Boolean props can be shorthand: `horizontal`
28
28
 
29
29
  ## Component Reference
@@ -33,13 +33,13 @@ Choose the component that best fits your data:
33
33
  Displays a single KPI metric as a large number.
34
34
 
35
35
  ```markdown
36
- <BigValue data={total_revenue} value="total_revenue" title="Revenue" />
36
+ <BigValue data={total_revenue} value="orders.total_revenue" title="Revenue" />
37
37
  ```
38
38
 
39
39
  | Prop | Type | Required | Description |
40
40
  |------|------|----------|-------------|
41
41
  | `data` | query ref | Yes | Query name (should return a single row) |
42
- | `value` | string | Yes | Measure field name to display |
42
+ | `value` | string | Yes | Fully qualified measure field name to display |
43
43
  | `title` | string | No | Label above the value |
44
44
  | `fmt` | string | No | Format preset or Excel code (e.g. `fmt="eur2"`, `fmt="$#,##0.00"`) |
45
45
 
@@ -48,16 +48,16 @@ Displays a single KPI metric as a large number.
48
48
  Renders a line chart, typically for time series. Supports multiple y columns and series splitting.
49
49
 
50
50
  ```markdown
51
- <LineChart data={monthly_revenue} x="created_at" y="total_revenue" title="Revenue Trend" />
52
- <LineChart data={trend} x="date" y="revenue,cases" />
53
- <LineChart data={revenue_by_type} x="created_at" y="total_revenue" series="type" />
51
+ <LineChart data={monthly_revenue} x="orders.created_at" y="orders.total_revenue" title="Revenue Trend" />
52
+ <LineChart data={trend} x="orders.created_at" y="orders.total_revenue,orders.count" />
53
+ <LineChart data={revenue_by_type} x="orders.created_at" y="orders.total_revenue" series="orders.type" />
54
54
  ```
55
55
 
56
56
  | Prop | Type | Required | Description |
57
57
  |------|------|----------|-------------|
58
58
  | `data` | query ref | Yes | Query name |
59
59
  | `x` | string | Yes | Field for x-axis (typically a time dimension) |
60
- | `y` | string | Yes | Field(s) for y-axis. Comma-separated for multiple (e.g. `y="revenue,cases"`) |
60
+ | `y` | string | Yes | Field(s) for y-axis. Comma-separated for multiple (e.g. `y="orders.total_revenue,orders.count"`) |
61
61
  | `title` | string | No | Chart title |
62
62
  | `series` | string | No | Column to split data into separate colored lines |
63
63
  | `type` | string | No | `"stacked"` for stacked lines (default: no stacking) |
@@ -68,17 +68,17 @@ Renders a line chart, typically for time series. Supports multiple y columns and
68
68
  Renders a vertical bar chart. Add `horizontal` for horizontal bars. Supports multi-series with stacked or grouped display.
69
69
 
70
70
  ```markdown
71
- <BarChart data={revenue_by_city} x="city" y="total_revenue" />
72
- <BarChart data={revenue_by_city} x="city" y="total_revenue" horizontal />
73
- <BarChart data={revenue_by_type} x="month" y="total_revenue" series="type" />
74
- <BarChart data={revenue_by_type} x="month" y="total_revenue" series="type" type="grouped" />
71
+ <BarChart data={revenue_by_city} x="orders.city" y="orders.total_revenue" />
72
+ <BarChart data={revenue_by_city} x="orders.city" y="orders.total_revenue" horizontal />
73
+ <BarChart data={revenue_by_type} x="orders.created_at" y="orders.total_revenue" series="orders.type" />
74
+ <BarChart data={revenue_by_type} x="orders.created_at" y="orders.total_revenue" series="orders.type" type="grouped" />
75
75
  ```
76
76
 
77
77
  | Prop | Type | Required | Description |
78
78
  |------|------|----------|-------------|
79
79
  | `data` | query ref | Yes | Query name |
80
80
  | `x` | string | Yes | Field for category axis |
81
- | `y` | string | Yes | Field(s) for value axis. Comma-separated for multiple (e.g. `y="revenue,cases"`) |
81
+ | `y` | string | Yes | Field(s) for value axis. Comma-separated for multiple (e.g. `y="orders.total_revenue,orders.count"`) |
82
82
  | `title` | string | No | Chart title |
83
83
  | `horizontal` | boolean | No | Render as horizontal bar chart |
84
84
  | `series` | string | No | Column to split data into separate colored bars |
@@ -90,15 +90,15 @@ Renders a vertical bar chart. Add `horizontal` for horizontal bars. Supports mul
90
90
  Renders a filled area chart. Supports series splitting and stacked areas.
91
91
 
92
92
  ```markdown
93
- <AreaChart data={monthly_revenue} x="created_at" y="total_revenue" />
94
- <AreaChart data={revenue_by_source} x="created_at" y="total_revenue" series="source" type="stacked" />
93
+ <AreaChart data={monthly_revenue} x="orders.created_at" y="orders.total_revenue" />
94
+ <AreaChart data={revenue_by_source} x="orders.created_at" y="orders.total_revenue" series="orders.source" type="stacked" />
95
95
  ```
96
96
 
97
97
  | Prop | Type | Required | Description |
98
98
  |------|------|----------|-------------|
99
99
  | `data` | query ref | Yes | Query name |
100
100
  | `x` | string | Yes | Field for x-axis |
101
- | `y` | string | Yes | Field(s) for y-axis. Comma-separated for multiple (e.g. `y="revenue,cases"`) |
101
+ | `y` | string | Yes | Field(s) for y-axis. Comma-separated for multiple (e.g. `y="orders.total_revenue,orders.count"`) |
102
102
  | `title` | string | No | Chart title |
103
103
  | `series` | string | No | Column to split data into separate colored areas |
104
104
  | `type` | string | No | `"stacked"` for stacked areas (default: no stacking) |
@@ -109,7 +109,7 @@ Renders a filled area chart. Supports series splitting and stacked areas.
109
109
  Renders a pie/donut chart.
110
110
 
111
111
  ```markdown
112
- <PieChart data={by_status} name="status" value="count" title="Order Status" />
112
+ <PieChart data={by_status} name="orders.status" value="orders.count" title="Order Status" />
113
113
  ```
114
114
 
115
115
  | Prop | Type | Required | Description |
@@ -125,7 +125,7 @@ Renders query results as a sortable, paginated table. Click any column header to
125
125
 
126
126
  ```markdown
127
127
  <DataTable data={top_products} />
128
- <DataTable data={top_products} columns="name,revenue,count" />
128
+ <DataTable data={top_products} columns="orders.category,orders.total_revenue,orders.count" />
129
129
  <DataTable data={top_products} rows="25" />
130
130
  <DataTable data={top_products} rows="all" />
131
131
  ```
@@ -135,7 +135,7 @@ Renders query results as a sortable, paginated table. Click any column header to
135
135
  | `data` | query ref | Yes | Query name |
136
136
  | `columns` | string | No | Comma-separated list of columns to show (default: all) |
137
137
  | `title` | string | No | Table title |
138
- | `fmt` | string | No | Column format map: `fmt="revenue:eur2,date:shortdate"` |
138
+ | `fmt` | string | No | Column format map: `fmt="orders.total_revenue:eur2,orders.created_at:shortdate"` |
139
139
  | `rows` | string | No | Rows per page. Default `10`. Use `rows="all"` to disable pagination. |
140
140
 
141
141
  **Sorting:** Click a column header to sort ascending. Click again to sort descending. Null values always sort to the end. Numbers sort numerically, strings sort case-insensitively.
@@ -149,9 +149,9 @@ Renders query results as a sortable, paginated table. Click any column header to
149
149
  Consecutive `<BigValue>` components are automatically wrapped in a responsive grid — no `<Grid>` tag needed:
150
150
 
151
151
  ```markdown
152
- <BigValue data={total_revenue} value="total_revenue" title="Revenue" />
153
- <BigValue data={order_count} value="count" title="Orders" />
154
- <BigValue data={avg_order} value="avg_order_value" title="Avg Order" />
152
+ <BigValue data={total_revenue} value="orders.total_revenue" title="Revenue" />
153
+ <BigValue data={order_count} value="orders.count" title="Orders" />
154
+ <BigValue data={avg_order} value="orders.avg_order_value" title="Avg Order" />
155
155
  ```
156
156
 
157
157
  This renders as a 3-column row. The grid auto-sizes up to 4 columns based on the number of consecutive BigValues. For more control, use an explicit `<Grid>` tag.
@@ -162,9 +162,9 @@ Wrap components in a `<Grid>` tag to arrange them in columns:
162
162
 
163
163
  ```markdown
164
164
  <Grid cols="3">
165
- <BigValue data={total_orders} value="count" title="Orders" />
166
- <BigValue data={total_revenue} value="total_revenue" title="Revenue" />
167
- <BigValue data={avg_order} value="avg_order_value" title="Avg Order" />
165
+ <BigValue data={total_orders} value="orders.count" title="Orders" />
166
+ <BigValue data={total_revenue} value="orders.total_revenue" title="Revenue" />
167
+ <BigValue data={avg_order} value="orders.avg_order_value" title="Avg Order" />
168
168
  </Grid>
169
169
  ```
170
170
 
@@ -198,7 +198,7 @@ Values are auto-formatted by default — numbers get locale grouping (1,234.56),
198
198
  | `longdate` | `d mmmm yyyy` | 13 January 2025 |
199
199
  | `monthyear` | `mmm yyyy` | Jan 2025 |
200
200
 
201
- Any string that isn't a preset name is treated as a raw Excel format code (ECMA-376). For example: `fmt="revenue:$#,##0.00"`.
201
+ Any string that isn't a preset name is treated as a raw Excel format code (ECMA-376). For example: `fmt="orders.total_revenue:$#,##0.00"`.
202
202
 
203
203
  Note: Percentage presets (`pct`, `pct1`, `pct2`) multiply by 100 per Excel convention — 0.45 displays as "45%".
204
204
 
@@ -206,19 +206,19 @@ Note: Percentage presets (`pct`, `pct1`, `pct2`) multiply by 100 per Excel conve
206
206
 
207
207
  ```markdown
208
208
  <!-- BigValue with currency -->
209
- <BigValue data={total_revenue} value="total_revenue" title="Revenue" fmt="eur2" />
209
+ <BigValue data={total_revenue} value="orders.total_revenue" title="Revenue" fmt="eur2" />
210
210
 
211
211
  <!-- DataTable with per-column formatting -->
212
- <DataTable data={sales} fmt="total_revenue:usd2,created_at:shortdate,margin:pct1" />
212
+ <DataTable data={sales} fmt="orders.total_revenue:usd2,orders.created_at:shortdate,orders.margin:pct1" />
213
213
 
214
214
  <!-- Chart with formatted tooltips -->
215
- <BarChart data={monthly} x="month" y="revenue" yFmt="usd" />
216
- <LineChart data={trend} x="date" y="growth" yFmt="pct1" />
215
+ <BarChart data={monthly} x="orders.created_at" y="orders.total_revenue" yFmt="usd" />
216
+ <LineChart data={trend} x="orders.created_at" y="orders.growth" yFmt="pct1" />
217
217
  ```
218
218
 
219
219
  ## Field Names
220
220
 
221
- Component field names (e.g. `x="city"`, `value="total_revenue"`) use the **unqualified** measure or dimension name — the same names defined in your cube. For example, if your cube has `measures: [{ name: total_revenue, ... }]`, use `value="total_revenue"`.
221
+ All field names in component props must be **fully qualified** with the view or cube name — the same format used in query blocks. For example, use `value="orders.total_revenue"` not `value="total_revenue"`.
222
222
 
223
223
  ## See Also
224
224
 
@@ -4,6 +4,8 @@
4
4
 
5
5
  Bonnard provides admin-managed data governance — control which views, columns, and rows each group of users can access. Policies are configured in the web UI and enforced automatically across MCP queries and the API. Changes take effect within one minute.
6
6
 
7
+ > **Building a B2B product?** Governance is for managing _internal_ user access via the dashboard. For tenant isolation in customer-facing apps (where each customer sees only their data), see [security-context](security-context).
8
+
7
9
  ## How It Works
8
10
 
9
11
  ```
@@ -70,6 +72,21 @@ Policies configured in the web UI are stored in Supabase and injected into the q
70
72
 
71
73
  No YAML changes are needed — governance is fully managed through the dashboard.
72
74
 
75
+ ## Governance and Developer-Defined Policies
76
+
77
+ Governance policies from the dashboard are **merged** with any `access_policy` entries you define in your YAML model files. This lets you combine both approaches:
78
+
79
+ - **Developer-defined policies** — written in YAML, typically for B2B tenant isolation using `group: "*"` (matches all users, including SDK tokens)
80
+ - **Governance policies** — configured in the dashboard UI for internal user access control
81
+
82
+ When governance injects policies:
83
+
84
+ 1. If a view has governance policies **and** developer-defined `access_policy` entries, both are merged into a single list
85
+ 2. If a view has developer-defined `access_policy` but **no** governance policies, the developer entries are preserved as-is
86
+ 3. If a view has **neither**, it receives a default policy restricting access to ungoverned users
87
+
88
+ This means you can safely define tenant isolation in YAML and layer dashboard governance on top — neither overwrites the other.
89
+
73
90
  ## Best Practices
74
91
 
75
92
  1. **Start with broad access, then restrict** — give groups all views first, then fine-tune as needed
@@ -81,3 +98,4 @@ No YAML changes are needed — governance is fully managed through the dashboard
81
98
 
82
99
  - [querying.mcp](querying.mcp) — How AI agents query your semantic layer
83
100
  - [views](views) — Creating curated data views
101
+ - [security-context](security-context) — B2B multi-tenancy with security context
@@ -39,6 +39,41 @@ const result = await bonnard.sql<OrderRow>(
39
39
  // result.data is OrderRow[]
40
40
  ```
41
41
 
42
+ ## Multi-tenant queries
43
+
44
+ When building B2B apps where each customer should only see their own data, use **security context** with token exchange. Your server exchanges a secret key for a scoped token, then your frontend queries with that token — row-level filters are enforced automatically.
45
+
46
+ ```typescript
47
+ // Server-side: exchange secret key for a scoped token
48
+ const res = await fetch('https://app.bonnard.dev/api/sdk/token', {
49
+ method: 'POST',
50
+ headers: {
51
+ 'Authorization': `Bearer ${process.env.BONNARD_SECRET_KEY}`,
52
+ 'Content-Type': 'application/json',
53
+ },
54
+ body: JSON.stringify({
55
+ security_context: { tenant_id: currentCustomer.id },
56
+ }),
57
+ });
58
+ const { token } = await res.json();
59
+ // Pass token to the frontend
60
+ ```
61
+
62
+ ```typescript
63
+ // Client-side: query with the scoped token
64
+ const bonnard = createClient({
65
+ fetchToken: async () => token, // from your server
66
+ });
67
+
68
+ const result = await bonnard.query({
69
+ measures: ['orders.revenue'],
70
+ dimensions: ['orders.status'],
71
+ });
72
+ // Only returns rows where tenant_id matches — enforced server-side
73
+ ```
74
+
75
+ This requires an `access_policy` on your view with a `{securityContext.attrs.tenant_id}` filter. See [security-context](security-context) for the full setup guide.
76
+
42
77
  ## What you can build
43
78
 
44
79
  - **Custom dashboards** — Query your semantic layer from Next.js, React, or any frontend
@@ -0,0 +1,200 @@
1
+ # Security Context
2
+
3
+ > Implement multi-tenant data isolation for B2B apps using security context and access policies.
4
+
5
+ Security context lets you build customer-facing applications where each tenant only sees their own data. It works through the SDK's token exchange mechanism — your server sets the context, and row-level filters are enforced automatically on every query.
6
+
7
+ ## When to Use What
8
+
9
+ | Use case | Mechanism | Configured in |
10
+ |----------|-----------|---------------|
11
+ | Internal users — teams, roles, field/row restrictions | [Governance](governance) | Dashboard UI |
12
+ | B2B apps — each customer sees only their data | Security context | YAML model + SDK |
13
+ | Both — internal governance + tenant isolation | Both (merged) | Dashboard + YAML |
14
+
15
+ ## How It Works
16
+
17
+ ```
18
+ Your server Bonnard Database
19
+ │ │ │
20
+ ├─ POST /api/sdk/token │ │
21
+ │ { security_context: │ │
22
+ │ { tenant_id: "acme" } } ─┤ │
23
+ │ │ │
24
+ │◄─ { token, expires_at } ────┤ │
25
+ │ │ │
26
+ │ (pass token to frontend) │ │
27
+ │ │ │
28
+ ├─ query(measures, dims) ─────┤ │
29
+ │ Authorization: Bearer ... │ │
30
+ │ ├─ WHERE tenant_id = 'acme' ──►│
31
+ │ │ (injected automatically) │
32
+ │◄─ filtered results ─────────┤◄─────────────────────────────┤
33
+ ```
34
+
35
+ 1. Your server calls `exchangeToken()` with a `security_context` containing tenant attributes
36
+ 2. Bonnard returns a short-lived scoped token (5 min TTL, refreshable via `fetchToken`)
37
+ 3. The frontend queries using that token — the query engine injects row-level filters from the `access_policy` matching `{securityContext.attrs.X}` values
38
+ 4. Only matching rows are returned — tenants cannot see each other's data
39
+
40
+ ## Step-by-Step Setup
41
+
42
+ ### 1. Define access_policy in your view YAML
43
+
44
+ Add an `access_policy` entry with `group: "*"` (matches all users, including SDK tokens with empty groups) and a row-level filter referencing security context attributes:
45
+
46
+ ```yaml
47
+ views:
48
+ - name: orders
49
+ cubes:
50
+ - join_path: base_orders
51
+ includes: "*"
52
+
53
+ access_policy:
54
+ - group: "*"
55
+ row_level:
56
+ filters:
57
+ - member: tenant_id
58
+ operator: equals
59
+ values:
60
+ - "{securityContext.attrs.tenant_id}"
61
+ ```
62
+
63
+ The `{securityContext.attrs.tenant_id}` placeholder is replaced at query time with the value from the token's security context.
64
+
65
+ ### 2. Deploy your model
66
+
67
+ ```bash
68
+ bon deploy
69
+ ```
70
+
71
+ ### 3. Exchange a token server-side
72
+
73
+ In your API route or server action, exchange your secret key for a scoped token by calling the `/api/sdk/token` endpoint:
74
+
75
+ ```typescript
76
+ // In your API route handler:
77
+ export async function GET(request: Request) {
78
+ const tenantId = await getTenantFromSession(request);
79
+
80
+ const res = await fetch('https://app.bonnard.dev/api/sdk/token', {
81
+ method: 'POST',
82
+ headers: {
83
+ 'Authorization': `Bearer ${process.env.BONNARD_SECRET_KEY}`,
84
+ 'Content-Type': 'application/json',
85
+ },
86
+ body: JSON.stringify({
87
+ security_context: { tenant_id: tenantId },
88
+ }),
89
+ });
90
+
91
+ const { token } = await res.json();
92
+ return Response.json({ token });
93
+ }
94
+ ```
95
+
96
+ ### 4. Query from the frontend
97
+
98
+ ```typescript
99
+ import { createClient } from '@bonnard/sdk';
100
+
101
+ const bonnard = createClient({
102
+ fetchToken: async () => {
103
+ const res = await fetch('/api/bonnard-token');
104
+ const { token } = await res.json();
105
+ return token;
106
+ },
107
+ });
108
+
109
+ const result = await bonnard.query({
110
+ measures: ['orders.revenue', 'orders.count'],
111
+ dimensions: ['orders.status'],
112
+ });
113
+ // Only returns rows where tenant_id matches the exchanged context
114
+ ```
115
+
116
+ ## Multiple Filters
117
+
118
+ You can filter on multiple attributes. Each filter is AND'd:
119
+
120
+ ```yaml
121
+ access_policy:
122
+ - group: "*"
123
+ row_level:
124
+ filters:
125
+ - member: tenant_id
126
+ operator: equals
127
+ values:
128
+ - "{securityContext.attrs.tenant_id}"
129
+ - member: region
130
+ operator: equals
131
+ values:
132
+ - "{securityContext.attrs.region}"
133
+ ```
134
+
135
+ ```typescript
136
+ const res = await fetch('https://app.bonnard.dev/api/sdk/token', {
137
+ method: 'POST',
138
+ headers: {
139
+ 'Authorization': `Bearer ${process.env.BONNARD_SECRET_KEY}`,
140
+ 'Content-Type': 'application/json',
141
+ },
142
+ body: JSON.stringify({
143
+ security_context: { tenant_id: 'acme', region: 'eu' },
144
+ }),
145
+ });
146
+ const { token } = await res.json();
147
+ ```
148
+
149
+ ## Combining with Governance
150
+
151
+ Security context policies and governance policies are **merged**, not replaced. You can safely use both:
152
+
153
+ - `group: "*"` entries in YAML handle B2B tenant isolation (matches all users including SDK tokens)
154
+ - Governance policies from the dashboard handle internal user access control (field visibility, row filters by group)
155
+
156
+ When both are active on the same view, the final `access_policy` contains all entries. Cube evaluates them based on the user's group membership — SDK tokens have `groups: []`, so they match `group: "*"` but not named groups.
157
+
158
+ ```yaml
159
+ # Developer-defined in YAML — always active
160
+ access_policy:
161
+ - group: "*"
162
+ row_level:
163
+ filters:
164
+ - member: tenant_id
165
+ operator: equals
166
+ values:
167
+ - "{securityContext.attrs.tenant_id}"
168
+
169
+ # Governance adds these at runtime (configured in dashboard):
170
+ # - group: sales
171
+ # member_level:
172
+ # includes: [revenue, count]
173
+ # - group: finance
174
+ # member_level:
175
+ # includes: [margin, cost]
176
+ ```
177
+
178
+ ## Token Exchange Reference
179
+
180
+ **Endpoint:** `POST /api/sdk/token`
181
+
182
+ **Headers:** `Authorization: Bearer bon_sk_...` (your secret key)
183
+
184
+ | Body parameter | Type | Description |
185
+ |----------------|------|-------------|
186
+ | `security_context` | `Record<string, string>` | Key-value pairs. Keys must match `{securityContext.attrs.X}` placeholders in your access_policy. Max 20 keys, key max 64 chars, value max 256 chars. |
187
+ | `expires_in` | `number` | Token TTL in seconds. Min 60, max 3600, default 900. |
188
+
189
+ **Response:** `{ token: string, expires_at: string }`
190
+
191
+ **Token properties:**
192
+ - Default TTL: 15 minutes (configurable 1–60 min via `expires_in`)
193
+ - Renewable via `fetchToken` callback (SDK re-fetches automatically before expiry)
194
+ - Contains `groups: []` (empty) — matches `group: "*"` policies only
195
+
196
+ ## See Also
197
+
198
+ - [governance](governance) — Dashboard-managed access control for internal users
199
+ - [querying.sdk](querying.sdk) — SDK query reference
200
+ - [syntax.context-variables](syntax.context-variables) — Context variable syntax reference
@@ -142,12 +142,30 @@ dimensions:
142
142
  3. **Use COMPILE_CONTEXT** for deployment config — not for per-query logic
143
143
  4. **Test filter pushdown** — verify FILTER_PARAMS generates expected SQL
144
144
 
145
- ## Deprecated: SECURITY_CONTEXT
145
+ ## Row-Level Security via access_policy
146
146
 
147
- `SECURITY_CONTEXT` is deprecated. Use `query_rewrite` for security filtering instead.
147
+ For row-level filtering based on the current user or tenant, use `access_policy` with `{securityContext.attrs.X}` in filter values:
148
+
149
+ ```yaml
150
+ views:
151
+ - name: orders
152
+ access_policy:
153
+ - group: "*"
154
+ row_level:
155
+ filters:
156
+ - member: tenant_id
157
+ operator: equals
158
+ values:
159
+ - "{securityContext.attrs.tenant_id}"
160
+ ```
161
+
162
+ Security context attributes are set during token exchange (SDK) or via governance user attributes (dashboard). See [security-context](security-context) for the full B2B multi-tenancy guide.
163
+
164
+ > **Note:** The upstream `SECURITY_CONTEXT` SQL variable is deprecated. Use `access_policy` row-level filters with `{securityContext.attrs.X}` instead.
148
165
 
149
166
  ## See Also
150
167
 
151
168
  - syntax
152
169
  - syntax.references
153
170
  - cubes.extends
171
+ - security-context
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonnard/cli",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "bon": "./dist/bin/bon.mjs"