@askdb/core 1.0.0-beta.44 → 1.0.0-beta.46

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 (138) hide show
  1. package/README.md +20 -19
  2. package/dist/ask.d.ts +45 -45
  3. package/dist/ask.d.ts.map +1 -1
  4. package/dist/ask.js +153 -310
  5. package/dist/ask.js.map +1 -1
  6. package/dist/enrichment/index.d.ts +1 -1
  7. package/dist/enrichment/index.d.ts.map +1 -1
  8. package/dist/enrichment/prompt.d.ts +3 -3
  9. package/dist/enrichment/prompt.d.ts.map +1 -1
  10. package/dist/enrichment/prompt.js +43 -17
  11. package/dist/enrichment/prompt.js.map +1 -1
  12. package/dist/enrichment/suggest.d.ts +2 -2
  13. package/dist/enrichment/suggest.d.ts.map +1 -1
  14. package/dist/enrichment/suggest.js +3 -1
  15. package/dist/enrichment/suggest.js.map +1 -1
  16. package/dist/enrichment/types.d.ts +29 -9
  17. package/dist/enrichment/types.d.ts.map +1 -1
  18. package/dist/errors.d.ts +97 -6
  19. package/dist/errors.d.ts.map +1 -1
  20. package/dist/errors.js +21 -0
  21. package/dist/errors.js.map +1 -1
  22. package/dist/index.d.ts +7 -5
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +6 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/logging/askdb-logger.d.ts +2 -0
  27. package/dist/logging/askdb-logger.d.ts.map +1 -1
  28. package/dist/logging/create-askdb-logger.d.ts.map +1 -1
  29. package/dist/logging/create-askdb-logger.js +3 -0
  30. package/dist/logging/create-askdb-logger.js.map +1 -1
  31. package/dist/logging/log-events.d.ts +8 -0
  32. package/dist/logging/log-events.d.ts.map +1 -1
  33. package/dist/logging/log-events.js +8 -0
  34. package/dist/logging/log-events.js.map +1 -1
  35. package/dist/retrieval/synthesize-ddl.d.ts +7 -1
  36. package/dist/retrieval/synthesize-ddl.d.ts.map +1 -1
  37. package/dist/retrieval/synthesize-ddl.js +36 -33
  38. package/dist/retrieval/synthesize-ddl.js.map +1 -1
  39. package/dist/schema/normalize.d.ts +6 -2
  40. package/dist/schema/normalize.d.ts.map +1 -1
  41. package/dist/schema/normalize.js +1 -4
  42. package/dist/schema/normalize.js.map +1 -1
  43. package/dist/schema/v2/format.d.ts +28 -1
  44. package/dist/schema/v2/format.d.ts.map +1 -1
  45. package/dist/schema/v2/format.js +53 -19
  46. package/dist/schema/v2/format.js.map +1 -1
  47. package/dist/schema/v2/index.d.ts +4 -3
  48. package/dist/schema/v2/index.d.ts.map +1 -1
  49. package/dist/schema/v2/index.js +4 -3
  50. package/dist/schema/v2/index.js.map +1 -1
  51. package/dist/schema/v2/loader.d.ts +28 -7
  52. package/dist/schema/v2/loader.d.ts.map +1 -1
  53. package/dist/schema/v2/loader.js +220 -96
  54. package/dist/schema/v2/loader.js.map +1 -1
  55. package/dist/schema/v2/mentions.d.ts +12 -0
  56. package/dist/schema/v2/mentions.d.ts.map +1 -1
  57. package/dist/schema/v2/mentions.js +65 -11
  58. package/dist/schema/v2/mentions.js.map +1 -1
  59. package/dist/schema/v2/save-file.d.ts +56 -0
  60. package/dist/schema/v2/save-file.d.ts.map +1 -0
  61. package/dist/schema/v2/save-file.js +138 -0
  62. package/dist/schema/v2/save-file.js.map +1 -0
  63. package/dist/schema/v2/tenant-policy-loader.d.ts +1 -10
  64. package/dist/schema/v2/tenant-policy-loader.d.ts.map +1 -1
  65. package/dist/schema/v2/tenant-policy-loader.js +65 -51
  66. package/dist/schema/v2/tenant-policy-loader.js.map +1 -1
  67. package/dist/schema/v2/tenant-policy.d.ts +7 -2
  68. package/dist/schema/v2/tenant-policy.d.ts.map +1 -1
  69. package/dist/schema/v2/tenant-policy.js +26 -0
  70. package/dist/schema/v2/tenant-policy.js.map +1 -1
  71. package/dist/sql/bind.d.ts +27 -7
  72. package/dist/sql/bind.d.ts.map +1 -1
  73. package/dist/sql/bind.js +33 -18
  74. package/dist/sql/bind.js.map +1 -1
  75. package/dist/sql/dialect-spec.d.ts +1 -0
  76. package/dist/sql/dialect-spec.d.ts.map +1 -1
  77. package/dist/sql/dialect-spec.js +7 -2
  78. package/dist/sql/dialect-spec.js.map +1 -1
  79. package/dist/sql/generate.d.ts +17 -11
  80. package/dist/sql/generate.d.ts.map +1 -1
  81. package/dist/sql/generate.js +65 -33
  82. package/dist/sql/generate.js.map +1 -1
  83. package/dist/sql/guardrail-decide.d.ts +58 -0
  84. package/dist/sql/guardrail-decide.d.ts.map +1 -0
  85. package/dist/sql/guardrail-decide.js +161 -0
  86. package/dist/sql/guardrail-decide.js.map +1 -0
  87. package/dist/sql/guardrails.d.ts +52 -0
  88. package/dist/sql/guardrails.d.ts.map +1 -0
  89. package/dist/sql/guardrails.js +100 -0
  90. package/dist/sql/guardrails.js.map +1 -0
  91. package/dist/sql/index.d.ts +2 -1
  92. package/dist/sql/index.d.ts.map +1 -1
  93. package/dist/sql/index.js +2 -1
  94. package/dist/sql/index.js.map +1 -1
  95. package/dist/sql/lexer.d.ts +13 -0
  96. package/dist/sql/lexer.d.ts.map +1 -1
  97. package/dist/sql/lexer.js +42 -7
  98. package/dist/sql/lexer.js.map +1 -1
  99. package/dist/sql/prompt-identifiers.d.ts +9 -0
  100. package/dist/sql/prompt-identifiers.d.ts.map +1 -1
  101. package/dist/sql/prompt-identifiers.js +22 -3
  102. package/dist/sql/prompt-identifiers.js.map +1 -1
  103. package/dist/sql/prompt.d.ts.map +1 -1
  104. package/dist/sql/prompt.js +5 -2
  105. package/dist/sql/prompt.js.map +1 -1
  106. package/dist/sql/rebind.d.ts +116 -0
  107. package/dist/sql/rebind.d.ts.map +1 -0
  108. package/dist/sql/rebind.js +215 -0
  109. package/dist/sql/rebind.js.map +1 -0
  110. package/dist/sql/sensitive-guardrail.d.ts +7 -2
  111. package/dist/sql/sensitive-guardrail.d.ts.map +1 -1
  112. package/dist/sql/sensitive-guardrail.js +14 -33
  113. package/dist/sql/sensitive-guardrail.js.map +1 -1
  114. package/dist/sql/tenant-guardrail.d.ts +3 -14
  115. package/dist/sql/tenant-guardrail.d.ts.map +1 -1
  116. package/dist/sql/tenant-guardrail.js +16 -65
  117. package/dist/sql/tenant-guardrail.js.map +1 -1
  118. package/dist/sql/tenant-placeholders.d.ts +40 -14
  119. package/dist/sql/tenant-placeholders.d.ts.map +1 -1
  120. package/dist/sql/tenant-placeholders.js +92 -48
  121. package/dist/sql/tenant-placeholders.js.map +1 -1
  122. package/dist/sql/tenant-policy-warnings.d.ts +8 -0
  123. package/dist/sql/tenant-policy-warnings.d.ts.map +1 -0
  124. package/dist/sql/tenant-policy-warnings.js +21 -0
  125. package/dist/sql/tenant-policy-warnings.js.map +1 -0
  126. package/dist/sql/tenant-scope-expand.d.ts +46 -0
  127. package/dist/sql/tenant-scope-expand.d.ts.map +1 -0
  128. package/dist/sql/tenant-scope-expand.js +101 -0
  129. package/dist/sql/tenant-scope-expand.js.map +1 -0
  130. package/dist/sql/tsql-batch.d.ts +56 -0
  131. package/dist/sql/tsql-batch.d.ts.map +1 -0
  132. package/dist/sql/tsql-batch.js +246 -0
  133. package/dist/sql/tsql-batch.js.map +1 -0
  134. package/dist/sql/validate.d.ts +18 -3
  135. package/dist/sql/validate.d.ts.map +1 -1
  136. package/dist/sql/validate.js +127 -8
  137. package/dist/sql/validate.js.map +1 -1
  138. package/package.json +2 -2
package/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # `@askdb/core`
2
2
 
3
- Dialect-agnostic NL→SQL pipeline for AskDB. Provides `ask()` orchestration, schema/IR types, modes, logging, and retrieval input. Bring your own dialect adapter (e.g. `@askdb/postgres`) and your own model.
3
+ NL→SQL pipeline for AskDB. Provides `ask()` orchestration, schema/IR types, modes, logging, retrieval input, and the built-in dialect specs (PostgreSQL, CockroachDB, MySQL, MariaDB, SQLite, SQL Server). Pass a dialect id such as `"postgres"` and bring your own model; the engine packages (`@askdb/postgres`, …) add introspection, not SQL generation ([ADR 0029](https://github.com/Ygilany/AskDB/blob/main/docs/adrs/0029-core-owns-built-in-dialect-specs.md)).
4
4
 
5
5
  > **Status:** pre-release beta (`1.0.0-beta.x`); public APIs may still change before 1.0. `0.3.0` moved the Postgres dialect and `@askdb/core/postgres` to `@askdb/postgres`. Runtime AI provider construction lives in `@askdb/ai`; core remains BYO-model and does not read `process.env`. See [`docs/adrs/0002-integration-package-layout.md`](https://github.com/Ygilany/AskDB/blob/main/docs/adrs/0002-integration-package-layout.md), [`docs/adrs/0005-askdb-config-and-env-bootstrap.md`](https://github.com/Ygilany/AskDB/blob/main/docs/adrs/0005-askdb-config-and-env-bootstrap.md), and [`docs/adrs/0006-ai-provider-integration-strategy.md`](https://github.com/Ygilany/AskDB/blob/main/docs/adrs/0006-ai-provider-integration-strategy.md).
6
6
 
7
- > **Security model:** `ask()` returns SQL and never executes it. Its SQL checks (single `SELECT`/`WITH` statement (with a SQL Server caveat), no comments, no write/DDL keywords, no known side-effecting functions, and the tenant and sensitive-column checks) are heuristic defense in depth that catch common model mistakes. The read-only and sensitive-column checks use a dialect-aware lexer; the tenant check uses a simpler scanner of its own ([#342](https://github.com/Ygilany/AskDB/issues/342)). They are not a SQL parser and not a security boundary. Run generated SQL under a read-only, least-privilege database role and enforce tenant isolation in the database. See [Safety boundaries](https://askdb.tools/concepts/safety-boundaries/).
7
+ > **Security model:** `ask()` returns SQL and never executes it. Its SQL checks (single `SELECT`/`WITH` statement (with a SQL Server caveat), no comments, no write/DDL keywords, no locking reads, no known side-effecting functions, and the tenant and sensitive-column checks) are heuristic defense in depth that catch common model mistakes. The read-only and sensitive-column checks use a dialect-aware lexer; the tenant check uses a simpler scanner of its own ([#342](https://github.com/Ygilany/AskDB/issues/342)). They are not a SQL parser and not a security boundary. Run generated SQL under a read-only, least-privilege database role and enforce tenant isolation in the database. See [Safety boundaries](https://askdb.tools/concepts/safety-boundaries/).
8
8
 
9
9
  ## Install
10
10
 
11
11
  ```bash
12
12
  # `ai` (Vercel AI SDK) is a required peer dependency — your app owns its version
13
13
  pnpm add @askdb/core ai
14
- # Plus a dialect adapter for the engine you target:
14
+ # Only to introspect a database (live, or offline from an export), its engine package:
15
15
  pnpm add @askdb/postgres
16
16
  # Plus a model — either construct one directly with an AI SDK provider:
17
17
  pnpm add @ai-sdk/openai
@@ -21,7 +21,7 @@ pnpm add @askdb/ai
21
21
 
22
22
  `ai` is a **peer dependency** (`^6.0.0 || ^7.0.51`: AI SDK 6, or 7.0.51 and later), not a bundled dependency: `ask()` receives a `LanguageModel` your app constructs, so core must use the same `ai` instance your app does. Hosts on AI SDK 6 (e.g. `@ai-sdk/openai@3`) and AI SDK 7 (`@ai-sdk/openai@4`) are both supported. The config-driven path (`@askdb/ai` and `@askdb/client`) currently requires AI SDK 7.
23
23
 
24
- `ai` is a required peer, not an optional one, and core loads it at import time. So any package that depends on `@askdb/core` needs `ai` installed, even for introspection only. That covers `@askdb/client`, `@askdb/connectors`, `@askdb/introspect`, the engine packages, `@askdb/prisma`, `@askdb/enrich` and `@askdb/rag`. npm 7+ and pnpm install the missing peer for you. Yarn doesn't, so add `ai` yourself. ADR 0006 records why (2026-09 amendment).
24
+ `ai` is a required peer, not an optional one, and core loads it at import time. So any package that depends on `@askdb/core` needs `ai` installed, even for introspection only. That covers `@askdb/client`, `@askdb/introspect`, the engine packages, `@askdb/prisma`, `@askdb/enrich` and `@askdb/rag`. npm 7+ and pnpm install the missing peer for you. Yarn doesn't, so add `ai` yourself. ADR 0006 records why (2026-09 amendment).
25
25
 
26
26
  `@askdb/core` itself does not depend on `pg`. The optional `pg` peer lives on `@askdb/postgres` for live Postgres introspection.
27
27
 
@@ -50,11 +50,10 @@ import { loadSchema } from "@askdb/core";
50
50
  const schema = loadSchema("./fixtures/schemas/orders-users.schema");
51
51
  ```
52
52
 
53
- ## Minimal example — with the Postgres dialect adapter
53
+ ## Minimal example — with the built-in Postgres dialect
54
54
 
55
55
  ```ts
56
56
  import { ask, loadSchema } from "@askdb/core";
57
- import { postgresDialect } from "@askdb/postgres";
58
57
  import { openai } from "@ai-sdk/openai";
59
58
 
60
59
  const schema = loadSchema("./fixtures/schemas/orders-users.schema");
@@ -63,7 +62,7 @@ const { sql } = await ask({
63
62
  question: "Top 5 customers by lifetime value?",
64
63
  schema,
65
64
  model: openai("gpt-4o"),
66
- dialect: postgresDialect,
65
+ dialect: "postgres", // or POSTGRES_DIALECT from @askdb/core
67
66
  });
68
67
  ```
69
68
 
@@ -82,16 +81,14 @@ const result = await ask({
82
81
  tenantScope,
83
82
  });
84
83
 
85
- // Execute either form.
86
- await pool.query(result.sql); // ready to run
87
- await pool.query(result.unboundSql!, result.params); // driver binding
84
+ // Execute either form. Tenant IDs are driver parameters by default (tenantSqlMode: "sql-params").
85
+ await pool.query(result.sql, result.tenantParams); // business values inlined, tenant IDs bound
86
+ await pool.query(result.unboundSql!, result.params); // every value bound
88
87
 
89
88
  // Render a form from result.parameters, then rebind locally — no model call.
90
- const rebound = bindPreparedQuery(result.preparedQuery!, {
91
- state_name: "Utah",
92
- ":tenant_agency_ids": authorizedAgencyIds,
93
- });
94
- await pool.query(rebound.sql);
89
+ // The guardrails run again on the template under this schema and scope.
90
+ const rebound = bindPreparedQuery(result.preparedQuery!, { state_name: "Utah" }, { schema, tenantScope });
91
+ await pool.query(rebound.sql, rebound.tenantParams ?? []); // like ask(): tenant IDs bound
95
92
  ```
96
93
 
97
94
  Key rules:
@@ -99,19 +96,23 @@ Key rules:
99
96
  - `parameterize` defaults to **true**. Set `parameterize: false` when the extra output tokens are not worth it.
100
97
  - If the model's extra blocks are missing or inconsistent, the extras are omitted and `result.sql` is unaffected. No new error reaches a caller who does not call `bindPreparedQuery()`.
101
98
  - The model decides what to parameterize, so a mistake changes the *form*, not the query. Constrain form inputs using returned `type` and `description`.
102
- - `bindPreparedQuery()` is mechanical: it checks names, types, and cardinality, and **does not authorize tenant IDs**. Authorization is the host's, exactly as when building `tenantScope`.
103
- - Callers using the new fields must execute with `params`, not `tenantParams`.
99
+ - `bindPreparedQuery(prepared, values, { schema, tenantScope?, sensitiveGuardrailMode?, acceptWarnings?, dialect? })` re-runs the read-only, tenant and sensitive checks on the stored template under the schema, scope and modes you pass, then binds. Pass `dialect`, the `DialectSpec` you gave `ask()`, when `preparedQuery.dialectRules` is `"host"` or missing: the rebind refuses rather than fall back to the built-in rules. Tenant IDs come from `tenantScope`, never from `values`. Under a policy with `enforcement: warn`, a template that fails the tenant check is refused unless `acceptWarnings` includes `"tenant"`. Authorizing `tenantScope` is the host's job, exactly as for `ask()`. Expand a `subtree` scope with `expandTenantScope()` first.
100
+ - Each SQL form runs with its own array: `unboundSql` with `params` (tenant IDs included), `sql` with `tenantParams`. Never swap or concatenate them. `tenantSqlMode: "sql-only"` inlines the tenant IDs in `sql` instead, and refuses one that isn't printable ASCII or contains a backslash. With a tenant scope and a custom `AskDialect`, or a `DialectSpec` whose `id` isn't built in, set `tenantSqlMode` yourself: there's no default.
104
101
  - List parameters are arity-stable in `unboundSql` only on PostgreSQL/CockroachDB (`= ANY($n)`); elsewhere a changed list length changes the marker count — rebind through `bindPreparedQuery()` rather than swapping the array.
105
102
  - Markers: `$N` (Postgres/CockroachDB), `?` (MySQL/MariaDB/SQLite), `@pN` (SQL Server, 0-based). Map values via `parameters[].markers` for SQL Server.
106
103
 
107
104
  ## What you get
108
105
 
109
106
  - `ask({ question, schema, model, dialect })` — generate checked SQL (plus optional `unboundSql` / `params` / `parameters` / `preparedQuery`).
110
- - `bindPreparedQuery(prepared, values)` — pure local rebind of a `PreparedQuery` (no model call).
107
+ - `bindPreparedQuery(prepared, values, guardrails)` — local rebind of a `PreparedQuery` (no model call), re-checked under the given schema and tenant scope.
108
+ - `expandTenantScope(policy, scope, resolveTenantDescendants)` — the `subtree` expansion `ask()` runs, for a rebind.
111
109
  - `AskDbLanguageModel` — AskDB's public name for the AI SDK language model contract.
112
- - `AskDialect` — the dialect adapter contract. `@askdb/postgres` exports a ready-made one.
110
+ - `DialectSpec` and the built-in specs (`POSTGRES_DIALECT`, `MYSQL_DIALECT`, …, `BUILT_IN_DIALECTS`) — the SQL rules for each built-in engine. The engine packages re-export them.
111
+ - `AskDialect` — the escape-hatch contract for a fully custom generator.
113
112
  - `loadSchema(path)` — load a Schema v2 directory, bundled JSON, or `schema.json` path.
114
113
  - `loadSchemaFromJson(raw)` — parse a Schema v2 bundled JSON string (e.g. from an env var).
114
+ - `readSchemaDirectory(dir)` — read a Schema v2 directory into its bundle form (what `askdb bundle` writes).
115
+ - `saveSchemaFile(schemaDir, relativePath, content)` — write one file inside a schema directory; refuses a symbolic link and replaces the target through a temp file.
115
116
  - `parseTableMarkdown` / `writeTableMarkdown` — round-trippable describable-layer parser/writer.
116
117
  - `extractSqlFromModelText` — generic fenced-code extractor (dialect-agnostic).
117
118
  - Structured logging hooks (`createAskDbLogger`, log-event contract).
package/dist/ask.d.ts CHANGED
@@ -7,8 +7,11 @@ import type { Retriever } from "./retrieval/types.js";
7
7
  import type { TenantScope } from "./schema/v2/tenant-policy.js";
8
8
  import { type BuiltInDialectId, type DialectSpec } from "./sql/dialect-spec.js";
9
9
  import { type TenantSqlOutputMode, type TenantBinding } from "./sql/tenant-placeholders.js";
10
+ import { type ResolveTenantDescendants, type TenantIdsByRoot } from "./sql/tenant-scope-expand.js";
10
11
  import { type SensitiveGuardrailMode, type SensitiveGuardrailResult } from "./sql/sensitive-guardrail.js";
12
+ import { type GuardrailVerdict } from "./errors.js";
11
13
  import { type PreparedQuery, type QueryParameterBinding, type QueryParamSlot } from "./sql/bind.js";
14
+ export type { ResolveTenantDescendants, TenantIdsByRoot };
12
15
  /** Options forwarded to a dialect's generator. Stable across dialects. */
13
16
  export type AskDialectGenerateOptions = {
14
17
  logger?: AskDbLogger;
@@ -39,9 +42,11 @@ export type AskDialectGenerateResult = {
39
42
  explain?: unknown;
40
43
  /**
41
44
  * Optional tenant guardrail result from a custom generator. When the schema has a
42
- * tenant policy, `ask()` merges its warnings into its own check of `sql` and
43
- * `unboundNamedSql` (before tenant rendering); it can add failures but never replace
44
- * or relax that check.
45
+ * tenant policy, its warnings become tenant findings (`form: "generator"`) next to
46
+ * `ask()`'s own check of `sql` and `unboundNamedSql`, decided under the policy's
47
+ * `enforcement`; `passed: false` with no warnings becomes one `UNPROVABLE_SCOPE`
48
+ * finding. It can add findings but never replace or relax that check. Without a tenant
49
+ * policy it is passed through to `result.tenantGuardrail` unchanged.
45
50
  */
46
51
  tenantGuardrail?: import("./sql/tenant-guardrail.js").TenantGuardrailResult;
47
52
  usage?: AskUsage;
@@ -67,8 +72,10 @@ export type AskDialectGenerateResult = {
67
72
  * policy, `ask()` runs the tenant guardrail on the returned `sql` (and
68
73
  * `unboundNamedSql`), with its `:tenant_<root>_ids` placeholders still in place,
69
74
  * then substitutes them, regardless of dialect; `strict` policies throw
70
- * `TenantGuardrailError`. A `tenantGuardrail` your
71
- * generator returns is merged into that result, never used in place of it.
75
+ * `TenantGuardrailError`. A `tenantGuardrail` your generator returns adds
76
+ * findings to that check (`form: "generator"`), never replaces it.
77
+ * - The sensitive-identifier check runs on your SQL too, unless
78
+ * `sensitiveGuardrailMode` is `"off"`.
72
79
  */
73
80
  export type AskDialect = {
74
81
  generate(question: string, schema: AnyNormalizedSchema, model: AskDbLanguageModel, options?: AskDialectGenerateOptions): Promise<AskDialectGenerateResult>;
@@ -92,28 +99,6 @@ export type AskGenerateDeps = {
92
99
  */
93
100
  providerOptions?: Record<string, unknown>;
94
101
  };
95
- /**
96
- * A subtree's IDs grouped by tenant root: each key is a root table id from the
97
- * tenant policy (`"table:public.clients"`), and its value holds IDs of that root's
98
- * own `tenantIdColumn`, never IDs of another root.
99
- *
100
- * Root tables have separate ID spaces, so client `5` and agency `5` are different
101
- * tenants. Keying the IDs by root lets `ask()` bind each root's IDs to that root's
102
- * own `:tenant_<label>_ids` placeholder.
103
- */
104
- export type TenantIdsByRoot = Readonly<Record<string, readonly string[]>>;
105
- /**
106
- * Host callback that expands a `subtree` tenant scope. Given the tenant root id and
107
- * the seed IDs, return the subtree's IDs grouped by root ({@link TenantIdsByRoot}):
108
- *
109
- * - under `tenantRoot`: the seeds and any same-table descendants (e.g. child
110
- * agencies through `agencies.parent_agency_id`);
111
- * - under each descendant root the policy declares (`roots[].parent` or
112
- * `hierarchy[]`): that root's IDs in the subtree.
113
- *
114
- * Pass it to `ask()` as {@link AskPipelineOptions.resolveTenantDescendants}.
115
- */
116
- export type ResolveTenantDescendants = (tenantRoot: string, seedIds: readonly string[]) => Promise<TenantIdsByRoot> | TenantIdsByRoot;
117
102
  export type AskPipelineOptions = {
118
103
  question: string;
119
104
  schema: AnyNormalizedSchema;
@@ -179,11 +164,19 @@ export type AskPipelineOptions = {
179
164
  */
180
165
  tenantScope?: TenantScope;
181
166
  /**
182
- * SQL output mode for tenant placeholders. Default `"sql-only"` inlines
183
- * escaped literal values. `"sql-params"` replaces them with the dialect's
184
- * driver markers (`$N` for Postgres/CockroachDB and custom `AskDialect`s, `?`
185
- * for MySQL/MariaDB/SQLite, `@pN` for SQL Server): run `sql` with
186
- * `tenantParams`, or `unboundSql` with `params`.
167
+ * SQL output mode for tenant placeholders. Default `"sql-params"`
168
+ * ({@link DEFAULT_TENANT_SQL_MODE}) replaces them with the dialect's driver
169
+ * markers (`$N` for Postgres/CockroachDB, `?` for MySQL/MariaDB/SQLite, `@pN` for
170
+ * SQL Server): run `sql` with `tenantParams`, or `unboundSql` with `params`, never
171
+ * with the two arrays concatenated. A custom `AskDialect`, or a `DialectSpec` whose
172
+ * `id` isn't built in, has no default: with a tenant scope it must set this
173
+ * (`"sql-params"` gives `$N` markers), or `ask()` throws `TenantScopeError`
174
+ * (`TENANT_SQL_MODE_REQUIRED`) before the model call, as it does on every dialect for
175
+ * a value that is neither `"sql-params"` nor `"sql-only"`.
176
+ * `"sql-only"` inlines the IDs as escaped literals, for a caller that can't bind
177
+ * parameters (a tool that takes one SQL string); it throws `TenantScopeError`
178
+ * (`UNESCAPABLE_TENANT_ID`) for a tenant ID that isn't printable ASCII or that
179
+ * contains a backslash.
187
180
  */
188
181
  tenantSqlMode?: TenantSqlOutputMode;
189
182
  /**
@@ -217,16 +210,17 @@ export type AskPipelineOptions = {
217
210
  * `askdb.pipeline.sensitive_sql_warning` is logged, but `ask()` still resolves.
218
211
  * `"strict"` throws {@link SensitiveReferenceError} instead. `"off"` skips the check.
219
212
  *
220
- * This only covers SQL produced by *this* call. Hosts that cache or replay SQL
221
- * should call `validateSensitiveReferences` on every execution path.
213
+ * Covers SQL produced by *this* call. A rebind with `bindPreparedQuery()` runs the
214
+ * check again on the stored template; hosts that store and replay `sql` itself should
215
+ * call `validateSensitiveReferences` on that path.
222
216
  */
223
217
  sensitiveGuardrailMode?: SensitiveGuardrailMode | "off";
224
218
  };
225
219
  export type AskPipelineResult = {
226
220
  /**
227
221
  * The model's bound SQL: business values inlined as literals. Tenant IDs are
228
- * inlined literals (`tenantSqlMode: "sql-only"`) or driver markers numbered from
229
- * the first slot (`"sql-params"`) — then execute it with `tenantParams`.
222
+ * driver markers numbered from the first slot (`tenantSqlMode: "sql-params"`, the
223
+ * default) — execute it with `tenantParams` — or inlined literals (`"sql-only"`).
230
224
  */
231
225
  sql: string;
232
226
  /**
@@ -247,18 +241,24 @@ export type AskPipelineResult = {
247
241
  preparedQuery?: PreparedQuery;
248
242
  explain?: unknown;
249
243
  /**
250
- * Sensitive-identifier guardrail result for `sql`. Present when the schema declares
251
- * at least one `sensitive` table/column and `sensitiveGuardrailMode` is not `"off"`.
244
+ * Every guardrail finding for the model's SQL, checked before AskDB renders it (its
245
+ * bound `sql` and `sql-unbound` block, with the `:tenant_<root>_ids` placeholders still
246
+ * in place), and the outcome: `allow`, or `warn` when a `warn`-mode check found
247
+ * something. A `deny` throws the check's typed error instead, with this verdict on it.
248
+ */
249
+ verdict: GuardrailVerdict;
250
+ /**
251
+ * Sensitive-identifier guardrail result, derived from {@link verdict}. Present when the
252
+ * schema declares at least one `sensitive` table/column and `sensitiveGuardrailMode` is
253
+ * not `"off"`.
252
254
  */
253
255
  sensitiveGuardrail?: SensitiveGuardrailResult;
254
256
  /**
255
- * Tenant guardrail result for the model's SQL, checked before tenant rendering: the
256
- * bound `sql` and its `sql-unbound` block, with the `:tenant_<root>_ids` placeholders
257
- * still in place. `sql` and `unboundSql` differ from those only by the substituted
258
- * tenant IDs or markers. Present whenever the schema has a tenant policy, for every
259
- * dialect form. In `strict` mode a failure
260
- * throws `TenantGuardrailError` instead, so a returned result is always `passed`
261
- * under `strict`.
257
+ * Tenant guardrail result, derived from {@link verdict}: its tenant findings, once each.
258
+ * `sql` and `unboundSql` differ from the checked forms only by the substituted tenant IDs
259
+ * or markers. Present whenever the schema has a tenant policy, for every dialect form.
260
+ * In `strict` mode a failure throws `TenantGuardrailError` instead, so a returned result
261
+ * is always `passed` under `strict`.
262
262
  */
263
263
  tenantGuardrail?: import("./sql/tenant-guardrail.js").TenantGuardrailResult;
264
264
  /**
package/dist/ask.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ask.d.ts","sourceRoot":"","sources":["../src/ask.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ask.d.ts","sourceRoot":"","sources":["../src/ask.ts"],"names":[],"mappings":"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"}