@vxil/cli 0.18.0 → 0.20.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.
@@ -10,12 +10,16 @@ export declare const HOOK_LIMITS: {
10
10
  readonly maxReadHooksPerCollection: 10;
11
11
  };
12
12
  /** The ONLY root variables an expression may reference. `caller` is the VERIFIED
13
- * end-user principal (https://vxil.com/docs/guide/09-security-and-multitenancy) — read-only and
14
- * populated ONLY on the READ path (runReadHooks); on the write path and in
15
- * server-caller mode it is null, exactly like `before` on a create. It carries
16
- * `caller.endUserId` (the verified session sub, or null) and `caller.principal`
17
- * ('end_user' | 'tenant'). It is the missing "caller/session context" a read
18
- * hook keys on to redact/derive per-viewer. */
13
+ * end-user principal (https://vxil.com/docs/guide/09-security-and-multitenancy) — read-only.
14
+ * It carries `caller.endUserId` (the verified session sub, or null),
15
+ * `caller.principal` ('end_user' | 'tenant') and `caller.roles` (the VERIFIED
16
+ * session role claims of the signed-in session — never a body or header
17
+ * value; null in server-caller mode). READ hooks always get it
18
+ * (server mode: `{ endUserId: null, principal: 'tenant', roles: null }`).
19
+ * WRITE hooks get it in verified end-user mode only; on a
20
+ * server write it is null, exactly like `before` on a create — so a role rule
21
+ * reads `isNull(caller) || contains(caller.roles, 'manager')`. `contains` on
22
+ * an array is exact element membership. */
19
23
  export declare const HOOK_ROOT_VARS: readonly ["item", "before", "now", "caller"];
20
24
  /** The ONLY callable functions. Each is pure + deterministic + bounded. */
21
25
  export declare const HOOK_FUNCTIONS: readonly ["min", "max", "abs", "round", "floor", "ceil", "sqrt", "pow", "sign", "len", "lower", "upper", "trim", "substr", "contains", "startsWith", "endsWith", "concat", "coalesce", "ifNull", "not", "isNull", "number", "string", "bool", "daysBetween", "yearsBetween"];
@@ -80,20 +84,25 @@ export declare function parseExpr(src: string): Node;
80
84
  /** Walk the AST and reject anything outside the allow-list, plus depth bounds.
81
85
  * Returns an array of human-readable errors (empty = safe). Pure, no eval. */
82
86
  export declare function validateAst(root: Node): string[];
83
- /** The VERIFIED caller/session context for read hooks (guide ch. 7). Read-only;
84
- * present only on the read path in end-user mode. In server mode / write path
85
- * the whole object is null. */
87
+ /** The VERIFIED caller/session context for Lane-A hooks (guide ch. 7).
88
+ * Read-only. Read hooks always get it; write hooks get it in verified end-user
89
+ * mode only (a server write passes null — see HOOK_ROOT_VARS). */
86
90
  export interface HookCaller {
87
91
  /** the verified end-user session sub, or null in server-caller mode. */
88
92
  endUserId: string | null;
89
93
  /** 'end_user' when a session was verified at the edge, else 'tenant'. */
90
94
  principal: 'end_user' | 'tenant';
95
+ /** the VERIFIED session role claims of the signed-in session; `[]` for a
96
+ * session without roles, null in server-caller mode. Optional so an older
97
+ * caller object still reads
98
+ * as "no roles". */
99
+ roles?: readonly string[] | null;
91
100
  }
92
101
  export interface HookContext {
93
102
  item: Record<string, unknown>;
94
103
  before: Record<string, unknown> | null;
95
104
  now: string;
96
- /** verified caller/session context (read path only); null otherwise. */
105
+ /** verified caller/session context; null on a server write. */
97
106
  caller?: HookCaller | null;
98
107
  }
99
108
  /** A mutable step counter SHARED across many evalExpr calls (the read path's
@@ -127,7 +136,10 @@ export interface HookRunResult {
127
136
  * Mutates a CLONE: derive hooks set fields; a validate hook that returns falsy
128
137
  * throws HookRejection (→ the caller maps to a 422 and the tx rolls back).
129
138
  * Throws HookEvalError on a runtime fault (also a clean 422, never a 500). */
130
- export declare function runWriteHooks(hooks: Record<string, HookDef> | undefined, collection: string, phase: 'create' | 'update', data: Record<string, unknown>, before: Record<string, unknown> | null, now: string): HookRunResult;
139
+ export declare function runWriteHooks(hooks: Record<string, HookDef> | undefined, collection: string, phase: 'create' | 'update', data: Record<string, unknown>, before: Record<string, unknown> | null, now: string,
140
+ /** the VERIFIED end-user caller (endUserId + principal + roles) in
141
+ * end-user mode; omitted / null on a server write (expressions see null). */
142
+ caller?: HookCaller | null): HookRunResult;
131
143
  export interface ReadHookResult {
132
144
  /** Surviving rows in input order. Each is a SHALLOW COPY with a copied `data`
133
145
  * bag — the caller's rows are never mutated and nothing here is persisted. */
@@ -154,8 +166,8 @@ export interface ReadHookResult {
154
166
  * fire here, and read events never fire in runWriteHooks. */
155
167
  export declare function runReadHooks(hooks: Record<string, HookDef> | undefined, collection: string, rows: ReadonlyArray<Record<string, unknown>>, now: string,
156
168
  /** verified caller/session context (guide ch. 7) — read-only, exposed to
157
- * expressions as `caller.endUserId` / `caller.principal`. Omitted ⇒ null
158
- * (server-caller mode). */
169
+ * expressions as `caller.endUserId` / `caller.principal` / `caller.roles`.
170
+ * Omitted ⇒ null (server-caller mode). */
159
171
  caller?: HookCaller | null): ReadHookResult;
160
172
  export interface ExprRef {
161
173
  root: 'item' | 'before' | 'now' | 'caller';
@@ -35,4 +35,22 @@ interface CdcRuleLike {
35
35
  }
36
36
  /** Cross-field rule for CmsConfig.cdc (called from validateFeatureConfig). */
37
37
  export declare function validateCdcConfig(cdc: Record<string, CdcRuleLike> | undefined): string[];
38
+ /** What a payload:'full' change-data rule must know about its target
39
+ * collection's read gates. `readRoleFields`: fields with a non-empty
40
+ * `readRoles`; `endUserAccess`: the collection's end-user access mode. */
41
+ export interface CdcCollectionGates {
42
+ readRoleFields?: readonly string[];
43
+ endUserAccess?: 'readwrite' | 'read' | 'none';
44
+ }
45
+ /**
46
+ * A `payload: 'full'` change-data rule inlines the stored row into a realtime
47
+ * frame that every subscriber of the channel receives — it does not go through
48
+ * the read projection. So it must never target a collection whose reads are
49
+ * gated: a field with `readRoles` (the frame would carry the gated value), or
50
+ * an `endUserAccess` of 'read' / 'none' (the frame would bypass the
51
+ * collection's end-user gate). Pure; one problem line per offending ENABLED
52
+ * rule (an unknown collection is skipped). The safe shape is `payload: 'ids'`
53
+ * and a re-read through the cms, where every gate applies.
54
+ */
55
+ export declare function cdcFullFramesOverGatedCollections(cdc: unknown, collections: Record<string, CdcCollectionGates>): string[];
38
56
  export {};
@@ -218,6 +218,7 @@ declare const OidcProviderSchema: import("@sinclair/typebox").TObject<{
218
218
  email: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
219
219
  name: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
220
220
  roles: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
221
+ rolePrefix: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
221
222
  }>>;
222
223
  allowedDomains: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
223
224
  autoLink: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
@@ -275,6 +276,7 @@ export declare const AuthConfigSchema: import("@sinclair/typebox").TObject<{
275
276
  email: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
276
277
  name: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
277
278
  roles: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
279
+ rolePrefix: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
278
280
  }>>;
279
281
  allowedDomains: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
280
282
  autoLink: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
@@ -319,6 +321,8 @@ export declare const AuthConfigSchema: import("@sinclair/typebox").TObject<{
319
321
  breachedPasswords: import("@sinclair/typebox").TBoolean;
320
322
  captchaSecretRef: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
321
323
  allowedRedirectOrigins: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
324
+ inviteOnly: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
325
+ allowedEmailDomains: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
322
326
  }>>;
323
327
  }>;
324
328
  /** The security bag as persisted (present ⇒ leaf defaults applied). */
@@ -633,6 +637,7 @@ export declare const RagConfigSchema: import("@sinclair/typebox").TObject<{
633
637
  topK: import("@sinclair/typebox").TInteger;
634
638
  mode: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"hybrid">, import("@sinclair/typebox").TLiteral<"vector">, import("@sinclair/typebox").TLiteral<"keyword">]>;
635
639
  minScore: import("@sinclair/typebox").TNumber;
640
+ minSimilarity: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
636
641
  rerank: import("@sinclair/typebox").TBoolean;
637
642
  }>;
638
643
  boosts: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
package/dist/config.d.ts CHANGED
@@ -121,6 +121,17 @@ export interface CollectionDef {
121
121
  * `vxil push --allow-destructive`. Any other value fails `vxil plan` /
122
122
  * `vxil push` (it never falls back to 'readwrite'). */
123
123
  endUserAccess?: 'readwrite' | 'read' | 'none';
124
+ /** Role slugs one of which a VERIFIED end user must hold to WRITE this
125
+ * collection (create, update, $inc, delete, publish, transaction step,
126
+ * filtered delete, batch write, and an end-user delete's cascade into it) —
127
+ * otherwise `403 role_required`. Reads are unaffected. Roles are the
128
+ * session's verified role claims (its orgs roles — the
129
+ * same ones `readRoles` and `caller.roles` in hooks see). Server keys are
130
+ * never affected. Absent / `[]` = ungated. Up to 16 slugs
131
+ * (`^[a-z0-9][a-z0-9_-]{0,31}$`). Stored on the collection (model data, not
132
+ * a config leaf) and carried by BOTH push paths; widening (clearing, or
133
+ * adding a role) needs `vxil push --allow-destructive`. */
134
+ writeRoles?: string[];
124
135
  /** Per-record action buttons (guide ch. 4): `[{ key, label, fn }]`, stored on
125
136
  * the collection (model data, not a config leaf). The dashboard renders one
126
137
  * button per action on each record row; `POST /v1/cms/items/:coll/:id/actions/