@kortexya/reasoninglayer 1.27.0 → 1.28.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/dist/index.js CHANGED
@@ -5,7 +5,7 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/config.ts
8
- var SDK_VERSION = "1.27.0";
8
+ var SDK_VERSION = "1.28.0";
9
9
  function resolveConfig(config) {
10
10
  if (!config.baseUrl) {
11
11
  throw new Error("ClientConfig.baseUrl is required");
@@ -100,6 +100,20 @@ var ConstraintViolationError = class extends ApiError {
100
100
  this.constraint = constraint;
101
101
  }
102
102
  };
103
+ var BulkRefusedError = class extends ApiError {
104
+ name = "BulkRefusedError";
105
+ /**
106
+ * The refused rows, each naming its index in the request's `terms` array.
107
+ *
108
+ * @remarks
109
+ * Never empty — the engine answers this shape only when it refused something.
110
+ */
111
+ rows;
112
+ constructor(message, body, headers, rows, errorCode) {
113
+ super(message, 422, body, headers, errorCode);
114
+ this.rows = rows;
115
+ }
116
+ };
103
117
  var RateLimitError = class extends ApiError {
104
118
  name = "RateLimitError";
105
119
  /** Seconds to wait before retrying, or null if not specified. */
@@ -189,6 +203,13 @@ function createApiError(status, body, headers) {
189
203
  constraint
190
204
  );
191
205
  }
206
+ case 422: {
207
+ const rows = bulkRefusalRows(body);
208
+ if (rows !== null) {
209
+ return new BulkRefusedError(message, body, headers, rows, errorCode);
210
+ }
211
+ return new ApiError(message, status, body, headers, errorCode);
212
+ }
192
213
  case 429: {
193
214
  const rl = parseRateLimitHeaders(headers);
194
215
  return new RateLimitError(message, body, headers, errorCode, rl.retryAfter, rl.limit, rl.remaining);
@@ -205,6 +226,23 @@ function isErrorBody(body) {
205
226
  const obj = body;
206
227
  return typeof obj.error === "string" && typeof obj.message === "string";
207
228
  }
229
+ function bulkRefusalRows(body) {
230
+ if (typeof body !== "object" || body === null) return null;
231
+ const obj = body;
232
+ if (!Array.isArray(obj.errors)) return null;
233
+ const rows = [];
234
+ for (const entry of obj.errors) {
235
+ if (typeof entry !== "object" || entry === null) return null;
236
+ const row = entry;
237
+ if (typeof row.index !== "number" || typeof row.message !== "string") return null;
238
+ rows.push({
239
+ index: row.index,
240
+ message: row.message,
241
+ ...typeof row.feature === "string" ? { feature: row.feature } : {}
242
+ });
243
+ }
244
+ return rows.length > 0 ? rows : null;
245
+ }
208
246
  function isConstraintViolationBody(body) {
209
247
  if (!isErrorBody(body)) return false;
210
248
  if (!("details" in body)) return false;
@@ -1639,7 +1677,7 @@ var Terms = class {
1639
1677
  ...params
1640
1678
  });
1641
1679
  /**
1642
- * @description Creates multiple terms in a single operation for efficiency. This is optimized for high-volume data loading scenarios and skips individual witness validation (constraint propagation runs once at the end). # Performance - Terms are added to the domain store in a single lock acquisition - Constraint propagation runs once after all terms are added - Much faster than calling add_term N times # Declarations and all-or-nothing (#238, #239) Every entry is held to the same declarations as `POST /terms`, with the same `422`. One refusal — a declaration violation or a bound-constraint violation — refuses the WHOLE batch and puts the store and the facade back: fresh ids removed, coreferenced entities restored to their pre-batch description. A batch may reference the client-minted ids it is itself creating: the resolver is the resident store unioned with the batch's own ids. When such an id then coreferences through a `@key`, every designator of it in the same batch is rewritten to the entity it merged into — the reference names what the write actually produced, never an id the coreference removed. `term_ids` carries the EFFECTIVE id per request position — the created id, or the existing entity's id when the entry coreferenced through a `@key`. # Authorization Requires X-Tenant-Id header. The tenant_id is taken from the header.
1680
+ * @description Creates multiple terms in a single operation for efficiency. This is optimized for high-volume data loading scenarios and skips individual witness validation (constraint propagation runs once at the end). # Performance - Terms are added to the domain store in a single lock acquisition - Constraint propagation runs once after all terms are added - Much faster than calling add_term N times # Declarations and all-or-nothing (#238, #239, #262) Every entry is held to the same declarations as `POST /terms`, with the same `422`. One refusal — a declaration violation or a bound-constraint violation — refuses the WHOLE batch and puts the store and the facade back: fresh ids removed, coreferenced entities restored to their pre-batch description. The refusal NAMES THE ROWS (#262): the body carries `errors[]`, one entry per refused row, each with its `index` in the request's `terms` array and the `feature` the refusal is about when it names one, so a client repairs the exact rows it sent instead of guessing which one a single-message refusal meant. `partial: true` (#271) writes the rows the checks did NOT refuse instead of refusing the batch for one of them. A real import holding one bad record had to be resent minus that record — a second full call, a second full round of constraint evaluation, and a window in which a survivor's reference target can be deleted between the two — although the engine already names every bad entry by index in one pass and therefore already knows which ones were fine. The answer is `201` when anything was written, with `term_ids` carrying one id per ACCEPTED row and the same `errors[]` beside it; `422` when every row was refused, because nothing was written and that is a refusal. ⛔ Two failures stay batch-wide under `partial`, because neither can be attributed to a row, and the flag does not pretend otherwise: the end-of-batch constraint propagation answers ONE `409` for the whole batch (`facade.process_events`), and a persistence failure reverts what the batch inserted (#239). `dry_run: true` (#262) runs every check the real write runs — conversion, declarations, coreference, events — and writes nothing: the store and the facade are put back inside the write lock, nothing persists, nothing is notified, no derivation is queued. A clean dry run answers `200` with the count the batch would have produced and NO `term_ids` (#268): the rollback has already discarded every id it minted for a fresh entry, and a second, real POST of the same body mints different ones — so the vector named nothing, in the very positions the field documents as storable. The ids that DO outlive a dry run are the entities a `@key` coreference would have merged into, and those are answered separately as `coreferenced_term_ids`. A refused dry run answers the same `errors[]` body the real write answers with. A batch may reference the client-minted ids it is itself creating: the resolver is the resident store unioned with the batch's own ids. When such an id then coreferences through a `@key`, every designator of it in the same batch is rewritten to the entity it merged into — the reference names what the write actually produced, never an id the coreference removed. On a real write `term_ids` carries the EFFECTIVE id per request position — the created id, or the existing entity's id when the entry coreferenced through a `@key`. It is ABSENT on a dry run. # Authorization Requires X-Tenant-Id header. The tenant_id is taken from the header.
1643
1681
  *
1644
1682
  * @tags terms
1645
1683
  * @name BulkAddTerms
@@ -1937,7 +1975,7 @@ var Inference = class {
1937
1975
  ...params
1938
1976
  });
1939
1977
  /**
1940
- * @description This drops the hydrated base facts, the forward-chain `persist_derived` facts and the residuation store, then forgets the hydration flag so the next request reloads the base facts from PostgreSQL. **It does not delete durable data.** The terms are the authority; this is their cache. That makes it the retraction primitive forward chaining otherwise lacks. Chaining is monotonic — delete a `blocks` edge and the derived "A blocks B" survives every later pass, so the KB keeps asserting a relationship the user removed. Clearing and re-chaining rebuilds the closure from the terms that actually exist. # History Until 2026-07-28 this handler enumerated every term in the tenant and deleted it from PostgreSQL, while calling itself "clear facts" and reporting `"Cleared N facts/rules"`. It is reachable with an ordinary tenant credential — unlike `/api/v1/admin/clear-tenant/{tenant_id}`, which gateways block — so a caller reading the name, the path or the response body had no way to know it was a tenant wipe. It destroyed a live tenant that way. Use `DELETE /api/v1/terms/{term_id}` to delete a term, and the admin route to wipe a tenant; deleting durable data must not be something an endpoint does as a side effect of its name. # Authorization Requires X-Tenant-Id header, and the path tenant must match it.
1978
+ * @description This drops the hydrated base facts, the forward-chain `keep_derived` facts and the residuation store, then forgets the hydration flag so the next request reloads the base facts from PostgreSQL. **It does not delete durable data.** The terms are the authority; this is their cache. That makes it the retraction primitive forward chaining otherwise lacks. Chaining is monotonic — delete a `blocks` edge and the derived "A blocks B" survives every later pass, so the KB keeps asserting a relationship the user removed. Clearing and re-chaining rebuilds the closure from the terms that actually exist. # History Until 2026-07-28 this handler enumerated every term in the tenant and deleted it from PostgreSQL, while calling itself "clear facts" and reporting `"Cleared N facts/rules"`. It is reachable with an ordinary tenant credential — unlike `/api/v1/admin/clear-tenant/{tenant_id}`, which gateways block — so a caller reading the name, the path or the response body had no way to know it was a tenant wipe. It destroyed a live tenant that way. Use `DELETE /api/v1/terms/{term_id}` to delete a term, and the admin route to wipe a tenant; deleting durable data must not be something an endpoint does as a side effect of its name. # Authorization Requires X-Tenant-Id header, and the path tenant must match it.
1941
1979
  *
1942
1980
  * @tags inference
1943
1981
  * @name ClearFacts
@@ -6679,7 +6717,7 @@ var Query = class {
6679
6717
  this.http = http;
6680
6718
  }
6681
6719
  /**
6682
- * @description Returns all terms with the specified sort OR any of its subtypes. This implements proper OSF polymorphic query semantics where querying a parent sort returns all instances of that sort and its descendants. ## Resolving `sort_name` A name can denote more than one id (see `sort_name_candidates`), and no cheap probe tells which of them the query can actually answer from: on the production adapter `get_sort` and `get_sort_ids_by_names` are bare reads of an in-memory cache with no persistence fallback, while the query's own `get_compatible_sorts` does fall back to Postgres. Confirming a candidate with `get_sort` would therefore 404 every tenant sort created before the last restart — a guard strictly stricter than the thing it guards. So the candidates are **tried** against the real query; only `SortNotFound` moves on to the next, every other failure is returned as-is. A phantom id (minted into the tenant lattice by ingestion and never persisted, #138) cannot be returned: the query authority refuses it and the loop skips past it. When no candidate answers, the sort is not queryable and the honest reply is 404 naming the sort the CALLER asked for — never a 400 leaking an internal `SortId` the caller never supplied. Every candidate that answers **contributes**; the answer is their union, deduplicated by term id. Registration now keeps a tenant to one sort per name (#139), so two answering candidates mean rows a pre-fix engine left behind: one name, two sorts, and the tenant's terms of that type divided between them. Stopping at the first — what this route did — returned one half and reported nothing about the other, because an id the caller never supplied going unqueried raises no error. In the ordinary case exactly one candidate answers and the route runs exactly one term query, as it always did.
6720
+ * @description Returns all terms with the specified sort OR any of its subtypes. This implements proper OSF polymorphic query semantics where querying a parent sort returns all instances of that sort and its descendants. ## Resolving `sort_name` A name can denote more than one id (see `sort_name_candidates`), and no cheap probe tells which of them the query can actually answer from: on the production adapter `get_sort` and `get_sort_ids_by_names` are bare reads of an in-memory cache with no persistence fallback, while the query's own `get_compatible_sorts` does fall back to Postgres. Confirming a candidate with `get_sort` would therefore 404 every tenant sort created before the last restart — a guard strictly stricter than the thing it guards. So the candidates are **tried** against the real query; only `SortNotFound` moves on to the next, every other failure is returned as-is. A phantom id (minted into the tenant lattice by ingestion and never persisted, #138) cannot be returned: the query authority refuses it and the loop skips past it. When no candidate answers, the sort is not queryable and the honest reply is 404 naming the sort the CALLER asked for — never a 400 leaking an internal `SortId` the caller never supplied. Every candidate that answers **contributes**; the answer is their union, deduplicated by term id. Registration now keeps a tenant to one sort per name (#139), so two answering candidates mean rows a pre-fix engine left behind: one name, two sorts, and the tenant's terms of that type divided between them. Stopping at the first — what this route did — returned one half and reported nothing about the other, because an id the caller never supplied going unqueried raises no error. In the ordinary case exactly one candidate answers and the route runs exactly one term query, as it always did. ## Conclusions, and what this route promises The answer is the tenant's durable extension of the browse closure UNIONED with the conclusions this process currently HOLDS — the same set OSFQL `MATCH` reads. That is what makes a rule sort readable here at all: no OSFQL path persists a conclusion, so the durable half is empty for a sort whose members exist only by derivation (#261). This route does NOT chain. It reports what the last chain left, so: * after a restart the derived half is empty until something chains again — `CHAIN`, `PROVE`, a retraction's re-chain, or `POST /api/v1/inference/forward-chain`; * a conclusion whose premise was withdrawn through a door that runs no truth maintenance is still answered until the next chain (`docs/OPEN_DEFECTS.md` #82). `total` is the size of the answer before `offset`/`limit`, counted — never a search bound. Rows are ordered by term id, so a page is stable. When the answer is EMPTY and the browsed sort is a rule conclusion sort, `note` says so and names the routes that materialise the conclusions. A bare `total: 0` reads as an authoritative count, and for a rule sort in a freshly-started process it is the one answer a caller must not take at face value — the same confusion #261 was filed about, one state later.
6683
6721
  *
6684
6722
  * @tags query
6685
6723
  * @name FindBySort
@@ -9592,7 +9630,7 @@ var Admin = class {
9592
9630
  ...params
9593
9631
  });
9594
9632
  /**
9595
- * @description Truncates the derived_facts table for the given tenant and queues a BootstrapAll event covering every analyzed rule. Asynchronous: returns the queue depth and current materialization LSN, not the final state. Concurrent rebuilds for the same tenant are rejected with 409 (per-tenant mutex). Full operational runbook (prerequisites, timings, monitoring, failure recovery, known limitations) lives in .claude/SUB_MS_FORWARD_CHAINING_STATUS.md, section 'Operator runbook — derived-facts rebuild'.
9633
+ * @description Re-materialises the tenant's conclusions SYNCHRONOUSLY: chains the resident store once, so every conclusion the rules prove and the store lacks comes back. `rematerialised` reports how many, and every read surface answers from that store — this is the one call that repairs a drifted tenant (#270). ONE DIRECTION: it does not remove a conclusion the rules no longer support, because the write doors' truth maintenance already does that on the write that changed the premise. It ALSO truncates the durable derived_facts table and queues a BootstrapAll event over every analyzed rule; that half is asynchronous, and `removed` / `rules_queued` / `materialization_lsn` describe it. Concurrent rebuilds for the same tenant are rejected with 409 (per-tenant mutex). Full operational runbook (prerequisites, timings, monitoring, failure recovery, known limitations) lives in .claude/SUB_MS_FORWARD_CHAINING_STATUS.md, section 'Operator runbook — derived-facts rebuild'.
9596
9634
  *
9597
9635
  * @tags admin
9598
9636
  * @name RebuildDerivedFacts
@@ -10038,6 +10076,24 @@ var Osfql = class {
10038
10076
  format: "json",
10039
10077
  ...params
10040
10078
  });
10079
+ /**
10080
+ * @description Takes the same body as `POST /api/v1/osfql` (the `query` matters; `atomic` decides whether the answer reports the atomic refusal) and answers: - `mutates` — whether ANY statement writes, folding every nested statement, so an `IF` whose THEN or ELSE branch writes reports `true`, and including an inline-write `MATCH` a first-statement classification would read as safe; - `atomic_refusal` — why the program cannot run as one atomic unit, with the same code the run would refuse with (`drop_sort_in_atomic_program`); - `statements` — one entry per statement with the ENGINE's classification (catalog id, risk tier, and a per-statement `mutates` — not a client-side tokenizer), the exact source text, the sorts named, nested entries for IF branches and `WITH` continuations, and — for each destructive statement — the affected-row count and up to 10 sample rows. The per-statement `mutates` is what a write gate reads: `CHAIN` and `RELEASE RESIDUATIONS` report `true` although they classify as `process_control`, while `MARK`, `CUT` and `SPACE` report `false`. No client has to keep its own table of which process-control statements write; `GET /api/v1/osfql/catalog` carries the same verdict per entry as `never` / `always` / `depends` (#266). The classification comes from the engine's own parse; the counts come from a read-only `MATCH` derived from the destructive statement's pattern and run against a CLONE of the tenant. Nothing runs against the live store, so nothing observable happens in the tenant afterwards. # Examples ```json { "query": "MATCH person(name: ?N); RETRACT person(name: \"Bob\");" } ``` ```json { "query": "DROP SORT person; INSERT person(name: \"Bob\");" } ```
10081
+ *
10082
+ * @tags osfql
10083
+ * @name PreviewOsfql
10084
+ * @summary Preview an OSFQL program: what each statement would do, decided without running anything (#257).
10085
+ * @request POST:/api/v1/osfql/preview
10086
+ * @secure
10087
+ */
10088
+ previewOsfql = (data, params = {}) => this.http.request({
10089
+ path: `/api/v1/osfql/preview`,
10090
+ method: "POST",
10091
+ body: data,
10092
+ secure: true,
10093
+ type: "application/json",
10094
+ format: "json",
10095
+ ...params
10096
+ });
10041
10097
  };
10042
10098
 
10043
10099
  // src/api-spec/generated/Context.ts
@@ -13227,6 +13283,45 @@ var Research = class {
13227
13283
  });
13228
13284
  };
13229
13285
 
13286
+ // src/normalizers/ontology-alignment.ts
13287
+ function AlignOntologyRequestFromFrontToApi(model) {
13288
+ return {
13289
+ domain_owl: model.domainOwl,
13290
+ targets: model.targets
13291
+ };
13292
+ }
13293
+ function AlignmentMatchDtoFromApiToFront(dto) {
13294
+ return {
13295
+ domainSort: dto.domain_sort,
13296
+ matchType: dto.match_type,
13297
+ targetCurie: dto.target_curie,
13298
+ targetLabel: dto.target_label
13299
+ };
13300
+ }
13301
+ function AlignmentConflictDtoFromApiToFront(dto) {
13302
+ return {
13303
+ domainSort: dto.domain_sort,
13304
+ targetA: dto.target_a,
13305
+ targetB: dto.target_b
13306
+ };
13307
+ }
13308
+ function ExternalMatchDtoFromApiToFront(dto) {
13309
+ return {
13310
+ matchType: dto.match_type,
13311
+ ontologyId: dto.ontology_id,
13312
+ source: dto.source
13313
+ };
13314
+ }
13315
+ function AlignOntologyResponseFromApiToFront(dto) {
13316
+ return {
13317
+ conflicts: dto.conflicts.map(AlignmentConflictDtoFromApiToFront),
13318
+ domainSorts: dto.domain_sorts,
13319
+ mappingTtl: dto.mapping_ttl,
13320
+ matches: dto.matches.map(AlignmentMatchDtoFromApiToFront),
13321
+ targetSorts: dto.target_sorts
13322
+ };
13323
+ }
13324
+
13230
13325
  // src/utils/records.ts
13231
13326
  function definedEntries(map) {
13232
13327
  return Object.entries(map).filter((entry) => entry[1] !== void 0);
@@ -13278,7 +13373,12 @@ function FeatureDescriptorDtoFromApiToFront(dto) {
13278
13373
  // `undefined` for a question the wire already answers.
13279
13374
  required: dto.required ?? false,
13280
13375
  constraint: dto.constraint ? ConstraintDtoFromApiToFront(dto.constraint) : void 0,
13281
- key: dto.key
13376
+ key: dto.key,
13377
+ expectedSortName: dto.expected_sort_name ?? void 0,
13378
+ minCount: dto.min_count ?? void 0,
13379
+ maxCount: dto.max_count ?? void 0,
13380
+ cardinalityOrigin: dto.cardinality_origin ?? void 0,
13381
+ annotations: definedRecord2(dto.annotations)
13282
13382
  };
13283
13383
  }
13284
13384
  function FeatureDescriptorDtoFromFrontToApi(model) {
@@ -13288,7 +13388,18 @@ function FeatureDescriptorDtoFromFrontToApi(model) {
13288
13388
  expected_type_hint: model.expectedTypeHint ?? void 0,
13289
13389
  required: model.required,
13290
13390
  constraint: model.constraint ? ConstraintDtoFromFrontToApi(model.constraint) : void 0,
13291
- key: model.key
13391
+ key: model.key,
13392
+ expected_sort_name: model.expectedSortName ?? void 0,
13393
+ min_count: model.minCount ?? void 0,
13394
+ max_count: model.maxCount ?? void 0,
13395
+ cardinality_origin: model.cardinalityOrigin ?? void 0,
13396
+ annotations: model.annotations
13397
+ };
13398
+ }
13399
+ function CoextensiveDefinitionDtoFromApiToFront(dto) {
13400
+ return {
13401
+ definition: dto.definition,
13402
+ exampleCount: dto.example_count
13292
13403
  };
13293
13404
  }
13294
13405
  function BoundConstraintDtoFromApiToFront(dto) {
@@ -13370,7 +13481,17 @@ function SortDtoFromApiToFront(dto) {
13370
13481
  worldMode: dto.world_mode,
13371
13482
  annotations: definedRecord2(dto.annotations),
13372
13483
  pluginId: dto.plugin_id ?? void 0,
13373
- pluginLocalName: dto.plugin_local_name ?? void 0
13484
+ pluginLocalName: dto.plugin_local_name ?? void 0,
13485
+ altLabels: dto.alt_labels,
13486
+ hiddenLabels: dto.hidden_labels,
13487
+ scopeNote: dto.scope_note ?? void 0,
13488
+ related: dto.related,
13489
+ definition: dto.definition ?? void 0,
13490
+ coextensive: dto.coextensive?.map(CoextensiveDefinitionDtoFromApiToFront),
13491
+ externalMatches: dto.external_matches?.map(
13492
+ (match) => ExternalMatchDtoFromApiToFront(match)
13493
+ ),
13494
+ featureEquations: dto.feature_equations
13374
13495
  };
13375
13496
  }
13376
13497
  function SortInfoDtoFromApiToFront(dto) {
@@ -13382,7 +13503,20 @@ function SortInfoDtoFromApiToFront(dto) {
13382
13503
  function SortListResponseFromApiToFront(dto) {
13383
13504
  return {
13384
13505
  sorts: dto.sorts.map(SortDtoFromApiToFront),
13385
- count: dto.count
13506
+ count: dto.count,
13507
+ total: dto.total,
13508
+ offset: dto.offset
13509
+ };
13510
+ }
13511
+ function ListSortsQueryFromFrontToApi(query) {
13512
+ if (query === void 0) return void 0;
13513
+ return {
13514
+ include_system: query.includeSystem,
13515
+ limit: query.limit,
13516
+ llm_extracted: query.llmExtracted,
13517
+ name_prefix: query.namePrefix?.join(","),
13518
+ needs_review: query.needsReview,
13519
+ offset: query.offset
13386
13520
  };
13387
13521
  }
13388
13522
  function CreateSortRequestFromFrontToApi(model) {
@@ -13404,6 +13538,8 @@ function BulkSortDefinitionFromFrontToApi(model) {
13404
13538
  parents: model.parents,
13405
13539
  features: model.features?.map(FeatureDescriptorDtoFromFrontToApi),
13406
13540
  alt_labels: model.altLabels,
13541
+ hidden_labels: model.hiddenLabels,
13542
+ scope_note: model.scopeNote ?? void 0,
13407
13543
  description: model.description ?? void 0,
13408
13544
  world_mode: model.worldMode
13409
13545
  };
@@ -14013,13 +14149,60 @@ var SortsClient = class {
14013
14149
  return response.data;
14014
14150
  }
14015
14151
  /**
14016
- * List all sorts.
14152
+ * List every sort the tenant owns.
14153
+ *
14154
+ * @param requestOptions - Per-call transport overrides.
14155
+ * @returns The sorts, each with its feature declarations.
14156
+ * @throws {ApiError} When the engine refuses the request.
14157
+ *
14158
+ * @remarks
14159
+ * This asks for the whole listing and keeps only the array. A production
14160
+ * tenant can own 1 M+ sorts (~500 MB uncompressed), which one response
14161
+ * cannot deliver — use {@link SortsClient.listSortsPage} to window it, to
14162
+ * filter it, or to read the `total` that says when to stop.
14017
14163
  *
14018
- * @returns Array of sorts.
14164
+ * @example
14165
+ * ```typescript
14166
+ * const sorts = await client.sorts.listSorts();
14167
+ * ```
14019
14168
  */
14020
14169
  async listSorts(requestOptions) {
14021
- const response = await this.sorts.listSorts(this.tenantId, void 0, toRequestParams(requestOptions));
14022
- return SortListResponseFromApiToFront(response.data).sorts;
14170
+ return (await this.listSortsPage(void 0, requestOptions)).sorts;
14171
+ }
14172
+ /**
14173
+ * List the tenant's sorts, keeping the envelope — `count`, `total` and
14174
+ * `offset` beside the page.
14175
+ *
14176
+ * @param query - The window and filters over the listing.
14177
+ * @param requestOptions - Per-call transport overrides.
14178
+ * @returns The page, its length, the tenant's filtered total and the
14179
+ * offset the page starts at.
14180
+ * @throws {ApiError} When the engine refuses the request.
14181
+ *
14182
+ * @remarks
14183
+ * `count` is the length of THIS page; `total` is what the tenant owns after
14184
+ * filters, across all pages. Measured against the engine on a tenant
14185
+ * holding three sorts, `GET /api/v1/sorts/tenant/{id}` answers
14186
+ * `{"count":3,"total":3,"offset":0}`.
14187
+ *
14188
+ * @example
14189
+ * ```typescript
14190
+ * let offset = 0;
14191
+ * for (;;) {
14192
+ * const page = await client.sorts.listSortsPage({ limit: 500, offset });
14193
+ * consume(page.sorts);
14194
+ * offset += page.count;
14195
+ * if (page.total === undefined || offset >= page.total) break;
14196
+ * }
14197
+ * ```
14198
+ */
14199
+ async listSortsPage(query, requestOptions) {
14200
+ const response = await this.sorts.listSorts(
14201
+ this.tenantId,
14202
+ ListSortsQueryFromFrontToApi(query),
14203
+ toRequestParams(requestOptions)
14204
+ );
14205
+ return SortListResponseFromApiToFront(response.data);
14023
14206
  }
14024
14207
  /**
14025
14208
  * Bulk-create sorts with name-based parent references.
@@ -14626,6 +14809,11 @@ function toUntaggedValue(value) {
14626
14809
  return value.value.map(toUntaggedValue);
14627
14810
  }
14628
14811
  if (value.type === "Reference") {
14812
+ if (typeof value.value !== "string") {
14813
+ throw new ValidationError(
14814
+ `A @key reference designator (sort "${value.value.sortName}") cannot be a FEATURE value on an untagged inference endpoint: the untagged feature-value family has no designator variant. Name a whole term with { designator: \u2026 } where a TermInputDto is expected, or resolve this one to a term id.`
14815
+ );
14816
+ }
14629
14817
  return { termId: value.value };
14630
14818
  }
14631
14819
  if (value.type === "SortId") {
@@ -14689,6 +14877,18 @@ function ValueDtoFromApiToFront(dto) {
14689
14877
  value: dto.value.map(ValueDtoFromApiToFront)
14690
14878
  };
14691
14879
  }
14880
+ if (dto.type === "Reference") {
14881
+ if (typeof dto.value === "string") {
14882
+ return { type: "Reference", value: dto.value };
14883
+ }
14884
+ return {
14885
+ type: "Reference",
14886
+ value: {
14887
+ sortName: dto.value.sort_name,
14888
+ features: featuresFromApiToFront(dto.value.features)
14889
+ }
14890
+ };
14891
+ }
14692
14892
  if (dto.type === "PsiTerm") {
14693
14893
  const features = dto.value.features;
14694
14894
  return {
@@ -14779,6 +14979,18 @@ function ValueDtoFromFrontToApi(value) {
14779
14979
  value: value.value.map(ValueDtoFromFrontToApi)
14780
14980
  };
14781
14981
  }
14982
+ if (value.type === "Reference") {
14983
+ if (typeof value.value === "string") {
14984
+ return { type: "Reference", value: value.value };
14985
+ }
14986
+ return {
14987
+ type: "Reference",
14988
+ value: {
14989
+ sort_name: value.value.sortName,
14990
+ features: featuresFromFrontToApi(value.value.features)
14991
+ }
14992
+ };
14993
+ }
14782
14994
  if (value.type === "PsiTerm") {
14783
14995
  const features = value.value.features;
14784
14996
  return {
@@ -14864,7 +15076,10 @@ function TermDtoFromApiToFront(dto) {
14864
15076
  sortName: dto.sort_name ?? void 0,
14865
15077
  displayName: dto.display_name ?? void 0,
14866
15078
  referencedTerms,
14867
- origin: dto.origin
15079
+ origin: dto.origin,
15080
+ // `derived_by` is a uuid or `null`; the shipped field is `string |
15081
+ // undefined`, so a null collapses rather than travelling as a third state.
15082
+ ...dto.derived_by == null ? {} : { derivedBy: dto.derived_by }
14868
15083
  };
14869
15084
  }
14870
15085
  function WitnessProofDtoFromApiToFront(dto) {
@@ -14893,9 +15108,21 @@ function CreateTermRequestFromFrontToApi(model) {
14893
15108
  return {
14894
15109
  sort_id: model.sortId,
14895
15110
  owner_id: model.ownerId,
14896
- features: featuresFromFrontToApi(model.features)
15111
+ features: featuresFromFrontToApi(model.features),
15112
+ ...model.id === void 0 ? {} : { id: model.id }
14897
15113
  };
14898
15114
  }
15115
+ function CreateTermInputFromFrontToApi(model) {
15116
+ if ("sortName" in model) {
15117
+ return {
15118
+ sort_name: model.sortName,
15119
+ owner_id: model.ownerId,
15120
+ features: featuresFromFrontToApi(model.features),
15121
+ ...model.id === void 0 ? {} : { id: model.id }
15122
+ };
15123
+ }
15124
+ return CreateTermRequestFromFrontToApi(model);
15125
+ }
14899
15126
  function UpdateTermRequestFromFrontToApi(model) {
14900
15127
  return {
14901
15128
  features: featuresFromFrontToApi(model.features)
@@ -14903,15 +15130,34 @@ function UpdateTermRequestFromFrontToApi(model) {
14903
15130
  }
14904
15131
  function BulkAddTermsRequestFromFrontToApi(model) {
14905
15132
  return {
14906
- terms: model.terms.map(CreateTermRequestFromFrontToApi)
15133
+ terms: model.terms.map(CreateTermInputFromFrontToApi),
15134
+ dry_run: model.dryRun,
15135
+ partial: model.partial
14907
15136
  };
14908
15137
  }
14909
15138
  function BulkAddTermsResponseFromApiToFront(dto) {
14910
15139
  return {
14911
- termIds: dto.term_ids,
15140
+ // `term_ids` is absent on a dry run and shorter than the request under
15141
+ // `partial`, so it is optional on the shipped type rather than defaulted to
15142
+ // an empty array — an empty array would read as "nothing was created", and
15143
+ // a dry run over a clean batch creates nothing while reporting a non-zero
15144
+ // `termsAdded`. The two facts are different and both matter.
15145
+ ...dto.term_ids ? { termIds: dto.term_ids } : {},
15146
+ termsAdded: dto.terms_added,
15147
+ dryRun: dto.dry_run,
15148
+ ...dto.refused === void 0 || dto.refused === null ? {} : { refused: dto.refused },
15149
+ ...dto.errors ? { errors: dto.errors.map(BulkRowRefusalFromApiToFront) } : {},
15150
+ ...dto.coreferenced_term_ids ? { coreferencedTermIds: dto.coreferenced_term_ids } : {},
14912
15151
  processingTimeMs: dto.processing_time_ms
14913
15152
  };
14914
15153
  }
15154
+ function BulkRowRefusalFromApiToFront(dto) {
15155
+ return {
15156
+ index: dto.index,
15157
+ message: dto.message,
15158
+ ...dto.feature ? { feature: dto.feature } : {}
15159
+ };
15160
+ }
14915
15161
  function ClearTermsResponseFromApiToFront(dto) {
14916
15162
  return {
14917
15163
  message: dto.message,
@@ -14956,7 +15202,12 @@ function ValidatedUnifyResponseFromApiToFront(dto) {
14956
15202
  function TermListResponseFromApiToFront(dto) {
14957
15203
  return {
14958
15204
  terms: dto.terms.map(TermDtoFromApiToFront),
14959
- count: dto.count
15205
+ count: dto.count,
15206
+ // `total` is `null` on a route that does not page, and `note` is `null`
15207
+ // whenever the engine has no remark. Both collapse to absent so a consumer
15208
+ // tests one thing — presence — rather than two.
15209
+ ...dto.total == null ? {} : { total: dto.total },
15210
+ ...dto.note == null ? {} : { note: dto.note }
14960
15211
  };
14961
15212
  }
14962
15213
  function TermReferrerDtoFromApiToFront(dto) {
@@ -15073,10 +15324,9 @@ var TermsClient = class {
15073
15324
  * ```
15074
15325
  */
15075
15326
  async createTerm(request, requestOptions) {
15076
- const wireRequest = CreateTermRequestFromFrontToApi({
15077
- ...request,
15078
- features: convertFeatures(request.features)
15079
- });
15327
+ const wireRequest = CreateTermInputFromFrontToApi(
15328
+ "sortName" in request ? { ...request, features: convertFeatures(request.features) } : { ...request, features: convertFeatures(request.features) }
15329
+ );
15080
15330
  const response = await this.api.addTerm(wireRequest, toRequestParams(requestOptions));
15081
15331
  return TermResponseFromApiToFront(response.data);
15082
15332
  }
@@ -15251,13 +15501,91 @@ var TermsClient = class {
15251
15501
  }
15252
15502
  }
15253
15503
  /**
15254
- * Bulk-create terms.
15504
+ * Create many terms in one request, all-or-nothing by default.
15255
15505
  *
15256
- * @param request - Bulk creation request.
15257
- * @returns Bulk creation result with term UUIDs.
15506
+ * @param request - The rows, plus `dryRun` to check without writing and
15507
+ * `partial` to keep the rows that passed. Features may be plain JS values
15508
+ * or `Value.*` output.
15509
+ * @param requestOptions - Per-call request options.
15510
+ * @returns What was written (or, on a dry run, what WOULD be written):
15511
+ * `termsAdded`, `termIds` on a real write, `errors` beside them under
15512
+ * `partial`, `coreferencedTermIds` on a dry run.
15513
+ * @throws {@link BulkRefusedError} 422 when the batch was refused and
15514
+ * NOTHING was written. Read `error.rows` — one entry per refused row, each
15515
+ * naming its `index` in `request.terms`. This is the answer for a refused
15516
+ * default batch, for a refused dry run, and for a `partial` batch in which
15517
+ * every row was refused.
15518
+ * @throws {@link ApiError} 409 when the end-of-batch constraint propagation
15519
+ * refuses the batch as a whole. That verdict cannot be attributed to a row,
15520
+ * so `partial` does not split it.
15521
+ *
15522
+ * @remarks
15523
+ * **Serialization format: Tagged (`ValueDto`).** Plain feature values are
15524
+ * converted exactly as {@link TermsClient.createTerm} converts them.
15525
+ *
15526
+ * There are three outcomes, and they are distinguishable without reading a
15527
+ * status code:
15528
+ *
15529
+ * 1. **A clean write** — `201`. `termIds` holds one id per request row, in
15530
+ * request order; `termsAdded` equals its length; `errors` is absent.
15531
+ * Measured 2026-09-18, a 2-row clean batch:
15532
+ * `{"terms_added":2,"term_ids":["3d936f37-…","f4a77a0b-…"],
15533
+ * "processing_time_ms":38,"dry_run":false}`.
15534
+ * 2. **A partial write** — `201`, and only with `partial: true`. Some rows
15535
+ * landed. `termIds` holds one id per ACCEPTED row, so it is SHORTER than
15536
+ * `request.terms`, and `errors` sits beside it naming the refused ones.
15537
+ * Map a refusal back with `errors[].index`, never with a position in
15538
+ * `termIds`. Measured, a 2-row batch whose second row violates a declared
15539
+ * range:
15540
+ * `{"terms_added":1,"term_ids":["d6ba85e6-…"],"errors":[{"index":1,
15541
+ * "feature":"price","message":"Constraint violation: Feature 'price'
15542
+ * value violates its declared range/constraint"}],"refused":1,
15543
+ * "processing_time_ms":37,"dry_run":false}`.
15544
+ * 3. **A refusal** — `422`, thrown as {@link BulkRefusedError}. Nothing was
15545
+ * written. Measured, the same bad batch WITHOUT `partial`:
15546
+ * `{"code":"bulk_refused","message":"1 of 2 entries were refused; the
15547
+ * whole batch was refused and nothing was written","errors":[{"index":1,
15548
+ * "feature":"price","message":"Constraint violation: …"}]}`. With
15549
+ * `partial: true` and BOTH rows bad, the same `422`:
15550
+ * `"2 of 2 entries were refused; the whole batch was refused and nothing
15551
+ * was written"`.
15552
+ *
15553
+ * **`dryRun` runs every check and writes nothing**, and it answers in the
15554
+ * same two shapes. A clean dry run is `200` and reports NO `termIds` —
15555
+ * measured:
15556
+ * `{"terms_added":2,"coreferenced_term_ids":[],"processing_time_ms":0,
15557
+ * "dry_run":true}`. The absence is deliberate: the rollback discarded every
15558
+ * id it minted, a later real write mints different ones, so the vector would
15559
+ * name nothing. The ids that DO outlive a dry run are the existing entities
15560
+ * a `@key` coreference would have merged into, and they come back as
15561
+ * `coreferencedTermIds`. A dry run over a BAD batch throws
15562
+ * {@link BulkRefusedError} with the same per-row refusals a real write throws
15563
+ * — measured, identical body to outcome 3 — so a caller can validate an
15564
+ * import with one call and never touch the store.
15565
+ *
15566
+ * @example
15567
+ * ```typescript
15568
+ * // Validate an import without writing.
15569
+ * try {
15570
+ * const check = await client.terms.bulkCreateTerms({ terms: rows, dryRun: true });
15571
+ * console.log(`${check.termsAdded} rows would be written`); // no check.termIds
15572
+ * } catch (e) {
15573
+ * if (e instanceof BulkRefusedError) {
15574
+ * for (const row of e.rows) console.error(`row ${row.index}: ${row.message}`);
15575
+ * }
15576
+ * }
15577
+ *
15578
+ * // Write what passes, and report what did not.
15579
+ * const result = await client.terms.bulkCreateTerms({ terms: rows, partial: true });
15580
+ * console.log(`${result.termsAdded} written`, result.termIds);
15581
+ * for (const bad of result.errors ?? []) {
15582
+ * console.warn(`row ${bad.index} (${bad.feature}): ${bad.message}`);
15583
+ * }
15584
+ * ```
15258
15585
  */
15259
15586
  async bulkCreateTerms(request, requestOptions) {
15260
15587
  const wireRequest = BulkAddTermsRequestFromFrontToApi({
15588
+ ...request,
15261
15589
  terms: request.terms.map((t) => ({
15262
15590
  ...t,
15263
15591
  features: convertFeatures(t.features)
@@ -15267,34 +15595,66 @@ var TermsClient = class {
15267
15595
  return BulkAddTermsResponseFromApiToFront(response.data);
15268
15596
  }
15269
15597
  /**
15270
- * List terms for the authenticated tenant.
15598
+ * List one page of the tenant's terms, and say how many there are.
15271
15599
  *
15272
- * @param query - Optional paging and sort filter. Omit for every term.
15273
- * @returns The list of terms with total count.
15274
- * @throws {ApiError} If the request fails.
15275
- *
15276
- * @remarks
15277
- * Terms are enriched with sort names, display names, and referenced term summaries.
15278
- * Requires X-Tenant-Id header (set via client configuration).
15279
- * Uses the tagged {@link ValueDto} serialization format.
15280
- *
15281
- * `sortName` filters on the sort's committed name. For a plugin-contributed
15282
- * sort that is the namespaced form (`plugin:<plugin-name>:<local>`), which
15283
- * {@link SortDto.name} carries and {@link SortDto.pluginLocalName} maps back
15284
- * to the name its author wrote.
15285
- *
15286
- * @example
15287
- * ```typescript
15288
- * const result = await client.terms.listTerms();
15289
- * console.log(`Found ${result.count} terms`);
15600
+ * @param query - Paging, the sort filter, and `includeDerived`. Omit for
15601
+ * every term.
15602
+ * @param requestOptions - Per-call request options.
15603
+ * @returns The page in `terms`, its length in `count`, the size of the whole
15604
+ * answer in `total`, and an engine remark in `note`.
15605
+ * @throws {@link ApiError} If the request fails.
15606
+ *
15607
+ * @remarks
15608
+ * **Serialization format: Tagged (`ValueDto`).** Terms are enriched with sort
15609
+ * names, display names, and referenced-term summaries. Requires the
15610
+ * `X-Tenant-Id` header, which the client configuration sets.
15611
+ *
15612
+ * **Page off `total`, not `count`.** `count` is this page's length and
15613
+ * nothing else. Measured 2026-09-18 on a tenant holding 3 terms:
15614
+ * `GET /api/v1/terms?limit=1&offset=2` answered
15615
+ * `{"terms":[…one…],"count":1,"total":3}`. `total` is counted before the
15616
+ * window, so it is the number to compare an offset against.
15617
+ *
15618
+ * **When the page is empty, read `note` before you report "no results".**
15619
+ * The route neither chains nor persists conclusions, so for a sort whose
15620
+ * members exist only by derivation the honest answer in a freshly started
15621
+ * process is zero rows — and `total: 0` reads like an authoritative "no
15622
+ * members", which is false. Measured 2026-09-18, a tenant with `widget`,
15623
+ * subsort `premium_widget`, one `widget` fact and the rule
15624
+ * `widget(name: ?N) → premium_widget(name: ?N)`:
15625
+ * `GET /api/v1/terms?sort_name=premium_widget` answered
15626
+ * `{"terms":[],"count":0,"total":0,"note":"`premium_widget` is a rule
15627
+ * conclusion sort: its members are derived … An empty answer here does not
15628
+ * mean the sort has no members."}`. Without the rule, the same empty answer
15629
+ * carried no note.
15630
+ *
15631
+ * `sortName` filters on the sort's committed name, and on that sort EXACTLY
15632
+ * — a member of a subsort is not answered. For a plugin-contributed sort the
15633
+ * committed name is the namespaced form (`plugin:<plugin-name>:<local>`),
15634
+ * which {@link SortDto.name} carries and {@link SortDto.pluginLocalName}
15635
+ * maps back to the name its author wrote.
15636
+ *
15637
+ * @example
15638
+ * ```typescript
15639
+ * const first = await client.terms.listTerms({ sortName: 'person', limit: 50 });
15640
+ * if (first.terms.length === 0 && first.note) console.info(first.note);
15641
+ * for (let offset = 50; offset < (first.total ?? 0); offset += 50) {
15642
+ * const page = await client.terms.listTerms({ sortName: 'person', limit: 50, offset });
15643
+ * // …
15644
+ * }
15290
15645
  *
15291
- * // Only the first page of one sort's terms.
15292
- * const page = await client.terms.listTerms({ sortName: 'person', limit: 50 });
15646
+ * // The asserted rows alone — the engine includes conclusions by default.
15647
+ * const asserted = await client.terms.listTerms({ includeDerived: false });
15293
15648
  * ```
15294
15649
  */
15295
15650
  async listTerms(query, requestOptions) {
15296
15651
  const response = await this.api.listTerms(
15297
- query ? { limit: query.limit, offset: query.offset, sort_name: query.sortName } : void 0,
15652
+ query ? {
15653
+ limit: query.limit,
15654
+ offset: query.offset,
15655
+ sort_name: query.sortName,
15656
+ include_derived: query.includeDerived
15657
+ } : void 0,
15298
15658
  toRequestParams(requestOptions)
15299
15659
  );
15300
15660
  return TermListResponseFromApiToFront(response.data);
@@ -15323,12 +15683,25 @@ var TermsClient = class {
15323
15683
  // ─── Friendly Aliases ─────────────────────────────────────────────
15324
15684
  /**
15325
15685
  * Create multiple records in a single request.
15326
- * Alias for {@link bulkCreateTerms}.
15686
+ * Alias for {@link TermsClient.bulkCreateTerms}.
15687
+ *
15688
+ * @param request - The rows, plus `dryRun` and `partial`.
15689
+ * @param requestOptions - Per-call request options.
15690
+ * @returns What was written, exactly as {@link TermsClient.bulkCreateTerms}
15691
+ * returns it.
15692
+ * @throws {@link BulkRefusedError} 422 when nothing was written.
15693
+ *
15694
+ * @remarks
15695
+ * **Serialization format: Tagged (`ValueDto`).** Same call, friendlier name
15696
+ * — read {@link TermsClient.bulkCreateTerms} for the three outcomes and for
15697
+ * what a dry run does and does not report.
15327
15698
  *
15328
- * @param request - Bulk creation request.
15329
- * @returns Bulk creation result with term UUIDs.
15699
+ * @example
15700
+ * ```typescript
15701
+ * const result = await client.terms.createMany({ terms: rows, partial: true });
15702
+ * ```
15330
15703
  *
15331
- * @see bulkCreateTerms
15704
+ * @see {@link TermsClient.bulkCreateTerms}
15332
15705
  */
15333
15706
  async createMany(request, requestOptions) {
15334
15707
  return this.bulkCreateTerms(request, requestOptions);
@@ -15359,7 +15732,7 @@ var TermsClient = class {
15359
15732
  return paginateByOffset(
15360
15733
  async (window, perCall) => {
15361
15734
  const page = await this.listTerms({ ...query, ...window }, perCall);
15362
- return { items: page.terms };
15735
+ return page.total === void 0 ? { items: page.terms } : { items: page.terms, total: page.total };
15363
15736
  },
15364
15737
  options,
15365
15738
  requestOptions
@@ -15367,10 +15740,6 @@ var TermsClient = class {
15367
15740
  }
15368
15741
  };
15369
15742
  function convertFeatures(features) {
15370
- const values = Object.values(features);
15371
- if (values.length > 0 && values.every(isTaggedValueDto)) {
15372
- return features;
15373
- }
15374
15743
  return toTaggedFeatures(features);
15375
15744
  }
15376
15745
 
@@ -15421,6 +15790,14 @@ function TermInputDtoFromFrontToApi(model) {
15421
15790
  if ("termId" in model) {
15422
15791
  return { term_id: model.termId };
15423
15792
  }
15793
+ if ("designator" in model) {
15794
+ return {
15795
+ designator: {
15796
+ sort_name: model.designator.sortName,
15797
+ features: model.designator.features
15798
+ }
15799
+ };
15800
+ }
15424
15801
  if ("sortId" in model) {
15425
15802
  const inline = {
15426
15803
  sort_id: model.sortId,
@@ -15591,7 +15968,7 @@ function ForwardChainResponseFromApiToFront(dto) {
15591
15968
  iterations: dto.iterations,
15592
15969
  totalFacts: dto.total_facts,
15593
15970
  materializationTimeMs: dto.materialization_time_ms,
15594
- persistedCount: dto.persisted_count,
15971
+ keptCount: dto.kept_count,
15595
15972
  provenanceTags: dto.provenance_tags?.map(ProvenanceTagDtoFromApiToFront)
15596
15973
  };
15597
15974
  }
@@ -16028,7 +16405,7 @@ function BackwardChainRequestFromFrontToApi(model) {
16028
16405
  function ForwardChainRequestFromFrontToApi(model) {
16029
16406
  return {
16030
16407
  initial_facts: model.initialFacts?.map(TermInputDtoFromFrontToApi),
16031
- persist_derived: model.persistDerived,
16408
+ keep_derived: model.keepDerived,
16032
16409
  enable_provenance_tags: model.enableProvenanceTags,
16033
16410
  max_iterations: model.maxIterations,
16034
16411
  max_facts: model.maxFacts,
@@ -16391,7 +16768,19 @@ var InferenceClient = class {
16391
16768
  * Forward chaining starts from existing facts and applies rules to derive new facts,
16392
16769
  * repeating until no more new facts can be derived (fixpoint) or limits are reached.
16393
16770
  *
16394
- * If `persist_derived` is true, derived facts are permanently saved to the database.
16771
+ * `keepDerived: true` keeps the run's derivations RESIDENT so a later `MATCH`
16772
+ * reads them. A default run is rolled back, not merely unwritten — so this
16773
+ * route reports what it derived and leaves the store as it found it. Neither
16774
+ * setting is durable: to repair a tenant whose materialised set has drifted,
16775
+ * use OSFQL `CHAIN;` or {@link AdminClient.rebuildDerivedFacts}.
16776
+ *
16777
+ * `timeoutMs` is a server-side deadline in milliseconds, checked at every
16778
+ * fixpoint boundary and every rule application. Omitted means the engine's own
16779
+ * backstop (`OSFKB_FC_TIMEOUT_SECS`, 300 s by default); `0` opts out entirely.
16780
+ * The route answers `504` when the deadline passes, and the body says whether
16781
+ * a `keepDerived` run kept the partial derivation it had reached.
16782
+ *
16783
+ * @throws {ApiError} With status 504 when the derivation passed its deadline.
16395
16784
  */
16396
16785
  async forwardChain(request, requestOptions) {
16397
16786
  const wireRequest = {
@@ -16691,6 +17080,7 @@ function FindBySortRequestFromFrontToApi(model) {
16691
17080
  sort_name: model.sortName ?? void 0,
16692
17081
  filter: model.filter ?? void 0,
16693
17082
  limit: model.limit ?? void 0,
17083
+ offset: model.offset ?? void 0,
16694
17084
  include_derived: model.includeDerived
16695
17085
  };
16696
17086
  }
@@ -16831,11 +17221,97 @@ var QueryClient = class {
16831
17221
  return response.data.results.map(TermDtoFromApiToFront);
16832
17222
  }
16833
17223
  /**
16834
- * Find terms by sort ID, sort name, or with optional filter.
17224
+ * Browse a sort and everything below it, one page at a time.
16835
17225
  *
16836
- * @param request - Query by sort request. Accepts sort_id (UUID),
16837
- * sort_name (human-readable), and optional filter for feature-based filtering.
16838
- * @returns Array of matching terms (tagged ValueDto format).
17226
+ * @param request - `sortId` (UUID) or `sortName`, an optional feature
17227
+ * `filter`, the `limit`/`offset` window, and `includeDerived`.
17228
+ * @param requestOptions - Per-call request options.
17229
+ * @returns The page in `terms`, its length in `count`, the size of the whole
17230
+ * answer in `total`, and an engine remark in `note`.
17231
+ * @throws {@link ApiError} 404 when no sort of that name is queryable for
17232
+ * the tenant.
17233
+ *
17234
+ * @remarks
17235
+ * **Serialization format: Tagged (`ValueDto`).** This is the polymorphic
17236
+ * browse: a query on a sort answers that sort AND every subsort of it, which
17237
+ * is what distinguishes it from {@link TermsClient.listTerms}'s exact
17238
+ * `sortName` filter.
17239
+ *
17240
+ * **Page off `total`.** `count` is this page's length. `total` is the number
17241
+ * of rows matched BEFORE `offset` and `limit`, counted rather than estimated.
17242
+ *
17243
+ * **A page is stable.** The answer is ordered by term id before the window
17244
+ * is applied, so page 2 neither repeats nor skips a row of page 1. Measured
17245
+ * 2026-09-18 against a 2-member sort:
17246
+ * `POST /api/v1/query/by-sort {"sort_name":"widget","include_derived":true}`
17247
+ * answered ids `829ef5dc-…` then `d4f7a4f8-…` with
17248
+ * `{"count":2,"total":2}`; the same request plus `{"limit":1,"offset":1}`
17249
+ * answered `d4f7a4f8-…` alone with `{"count":1,"total":2}` — the second row,
17250
+ * and the same total.
17251
+ *
17252
+ * **The route does not chain.** It answers the tenant's durable extension
17253
+ * UNIONED with the conclusions this process currently HOLDS. After a restart
17254
+ * the derived half is empty until something chains again. So when the page
17255
+ * is empty, read `note` rather than trusting `total: 0`: measured
17256
+ * 2026-09-18, a tenant with `widget`, subsort `premium_widget`, one `widget`
17257
+ * fact and the rule `widget(name: ?N) → premium_widget(name: ?N)`,
17258
+ * `{"sort_name":"premium_widget"}` answered
17259
+ * `{"terms":[],"count":0,"total":0,"note":"`premium_widget` is a rule
17260
+ * conclusion sort: its members are derived … Materialise them with OSFQL
17261
+ * CHAIN, POST /api/v1/inference/forward-chain, or POST
17262
+ * /api/v1/admin/derived-facts/rebuild/{tenant_id} … An empty answer here
17263
+ * does not mean the sort has no members."}`. Before the rule existed, the
17264
+ * same empty query carried no note.
17265
+ *
17266
+ * @example
17267
+ * ```typescript
17268
+ * const page = await client.query.findBySortPage({
17269
+ * sortName: 'sales_order',
17270
+ * includeDerived: true,
17271
+ * limit: 25,
17272
+ * offset: 0,
17273
+ * });
17274
+ * if (page.terms.length === 0 && page.note) console.info(page.note);
17275
+ * console.log(`${page.count} of ${page.total}`);
17276
+ * ```
17277
+ *
17278
+ * @see {@link QueryClient.findBySort} — the deprecated array-returning form.
17279
+ */
17280
+ async findBySortPage(request, requestOptions) {
17281
+ const response = await this.api.findBySort(
17282
+ FindBySortRequestFromFrontToApi(request),
17283
+ toRequestParams(requestOptions)
17284
+ );
17285
+ return TermListResponseFromApiToFront(response.data);
17286
+ }
17287
+ /**
17288
+ * Browse a sort and everything below it, discarding the envelope.
17289
+ *
17290
+ * @deprecated Use {@link QueryClient.findBySortPage}, which returns the
17291
+ * engine's envelope. This method drops `count`, `total` and `note`, so a
17292
+ * caller cannot tell a full answer from a truncated one, cannot page, and
17293
+ * reads an empty array for a rule-conclusion sort with no way to see the
17294
+ * engine's explanation. It is kept so 1.27 callers keep compiling.
17295
+ *
17296
+ * @param request - `sortId` or `sortName`, an optional feature `filter`, the
17297
+ * `limit`/`offset` window, and `includeDerived`.
17298
+ * @param requestOptions - Per-call request options.
17299
+ * @returns The page's terms alone.
17300
+ * @throws {@link ApiError} 404 when no sort of that name is queryable for
17301
+ * the tenant.
17302
+ *
17303
+ * @remarks
17304
+ * **Serialization format: Tagged (`ValueDto`).** Identical request,
17305
+ * identical rows, identical ordering — see
17306
+ * {@link QueryClient.findBySortPage} for the measured ordering and paging
17307
+ * contract. The only difference is what is thrown away.
17308
+ *
17309
+ * @example
17310
+ * ```typescript
17311
+ * const terms = await client.query.findBySort({ sortName: 'sales_order' });
17312
+ * ```
17313
+ *
17314
+ * @see {@link QueryClient.findBySortPage}
16839
17315
  */
16840
17316
  async findBySort(request, requestOptions) {
16841
17317
  const response = await this.api.findBySort(FindBySortRequestFromFrontToApi(request), toRequestParams(requestOptions));
@@ -16943,10 +17419,6 @@ var QueryClient = class {
16943
17419
  }
16944
17420
  };
16945
17421
  function convertPattern(pattern) {
16946
- const values = Object.values(pattern.features);
16947
- if (values.length > 0 && values.every(isTaggedValueDto)) {
16948
- return pattern;
16949
- }
16950
17422
  return {
16951
17423
  sortId: pattern.sortId,
16952
17424
  features: toTaggedFeatures(pattern.features)
@@ -31397,6 +31869,11 @@ function OsfqlValueFromApiToFront(value) {
31397
31869
  return typeof payload === "string" ? { type: "string", value: payload } : void 0;
31398
31870
  case "boolean":
31399
31871
  return typeof payload === "boolean" ? { type: "boolean", value: payload } : void 0;
31872
+ // The engine's own RFC 3339 rendering, passed through as the string it is.
31873
+ // Parsing it to a `Date` here would reformat a response, which this layer
31874
+ // never does — and would lose the offset the engine chose to send.
31875
+ case "datetime":
31876
+ return typeof payload === "string" ? { type: "datetime", value: payload } : void 0;
31400
31877
  case "term_ref":
31401
31878
  return typeof payload === "string" ? { type: "term_ref", value: payload } : void 0;
31402
31879
  case "list": {
@@ -31530,6 +32007,7 @@ function OsfqlCatalogEntryFromApiToFront(dto) {
31530
32007
  syntax: dto.syntax,
31531
32008
  examples: dto.examples,
31532
32009
  risk: dto.risk,
32010
+ mutates: dto.mutates,
31533
32011
  execution: OsfqlCatalogExecutionFromApiToFront(dto.execution),
31534
32012
  uiAffinity: {
31535
32013
  display: dto.ui_affinity.display ?? void 0,
@@ -31548,6 +32026,74 @@ function OsfqlCatalogExecutionFromApiToFront(dto) {
31548
32026
  if (dto === "PlanOnly") return { status: "planOnly" };
31549
32027
  return { status: "partial", note: dto.Partial };
31550
32028
  }
32029
+ function OsfqlPreviewSortCountFromApiToFront(dto) {
32030
+ return { sort: dto.sort, count: dto.count };
32031
+ }
32032
+ function OsfqlPreviewAffectedFromApiToFront(dto) {
32033
+ const affected = { count: dto.count };
32034
+ if (dto.exact !== void 0) {
32035
+ affected.exact = dto.exact;
32036
+ }
32037
+ if (dto.sample_rows !== void 0) {
32038
+ const rows = OsfqlBindingsFromApiToFront(dto.sample_rows);
32039
+ if (rows === void 0) {
32040
+ return void 0;
32041
+ }
32042
+ affected.sampleRows = rows;
32043
+ }
32044
+ if (dto.by_sort !== void 0) {
32045
+ affected.bySort = dto.by_sort.map(OsfqlPreviewSortCountFromApiToFront);
32046
+ }
32047
+ return affected;
32048
+ }
32049
+ function OsfqlAtomicRefusalFromApiToFront(dto) {
32050
+ return { code: dto.code, message: dto.message };
32051
+ }
32052
+ function OsfqlPreviewStatementFromApiToFront(dto) {
32053
+ const statement = {
32054
+ index: dto.index,
32055
+ id: dto.id,
32056
+ statement: dto.statement,
32057
+ risk: dto.risk,
32058
+ mutates: dto.mutates,
32059
+ sorts: dto.sorts,
32060
+ source: dto.source
32061
+ };
32062
+ if (dto.affected !== void 0) {
32063
+ const affected = OsfqlPreviewAffectedFromApiToFront(dto.affected);
32064
+ if (affected === void 0) {
32065
+ return void 0;
32066
+ }
32067
+ statement.affected = affected;
32068
+ }
32069
+ if (dto.nested !== void 0) {
32070
+ const nested = [];
32071
+ for (const child of dto.nested) {
32072
+ const parsed = OsfqlPreviewStatementFromApiToFront(child);
32073
+ if (parsed === void 0) {
32074
+ return void 0;
32075
+ }
32076
+ nested.push(parsed);
32077
+ }
32078
+ statement.nested = nested;
32079
+ }
32080
+ return statement;
32081
+ }
32082
+ function OsfqlPreviewResponseFromApiToFront(dto) {
32083
+ const statements = [];
32084
+ for (const entry of dto.statements) {
32085
+ const parsed = OsfqlPreviewStatementFromApiToFront(entry);
32086
+ if (parsed === void 0) {
32087
+ return void 0;
32088
+ }
32089
+ statements.push(parsed);
32090
+ }
32091
+ const response = { mutates: dto.mutates, statements };
32092
+ if (dto.atomic_refusal !== void 0) {
32093
+ response.atomicRefusal = dto.atomic_refusal ? OsfqlAtomicRefusalFromApiToFront(dto.atomic_refusal) : dto.atomic_refusal;
32094
+ }
32095
+ return response;
32096
+ }
31551
32097
 
31552
32098
  // src/resources/osfql.ts
31553
32099
  var OsfqlClient = class {
@@ -31623,6 +32169,107 @@ var OsfqlClient = class {
31623
32169
  }
31624
32170
  return parsed;
31625
32171
  }
32172
+ /**
32173
+ * Preview an OSFQL program: what each statement would do, decided without
32174
+ * running anything.
32175
+ *
32176
+ * @param query - The OSFQL program text (one or more statements separated by `;`).
32177
+ * @param options - Optional request options. Only `atomic` changes the answer:
32178
+ * it decides whether `atomicRefusal` is reported. `reactive`, `maxRows` and
32179
+ * `timeoutMs` are accepted for wire parity with {@link execute} and are
32180
+ * ignored by the route.
32181
+ * @param requestOptions - Per-call transport options (timeout, signal, headers).
32182
+ * @returns The per-statement classification, the affected-row reports, and the
32183
+ * atomic refusal when there is one.
32184
+ * @throws {ApiError} If the program does not parse (HTTP 400, `OsfqlErrorResponse`),
32185
+ * or the request otherwise fails.
32186
+ * @throws {ReasoningLayerError} If a sample row carries a value that does not satisfy
32187
+ * the published {@link OsfqlValue} contract.
32188
+ *
32189
+ * @remarks
32190
+ * **Why call this instead of classifying the program client-side.** Three
32191
+ * answers only the engine's own parse can give:
32192
+ *
32193
+ * 1. **Per-statement `mutates`.** A client keeps no table of which
32194
+ * process-control statements write. Measured on the dev engine: `CHAIN` and
32195
+ * `RELEASE RESIDUATIONS` report `mutates: true`, while `MARK ?m1`, `CUT` and
32196
+ * `SPACE CREATE ?S` report `false` — all five are `risk: "process_control"`.
32197
+ * In the other direction, `MATCH customer(name: ?N) INSERT vip(name: ?N)`
32198
+ * is `risk: "read"` with `mutates: true`. Read `mutates`, never `risk`.
32199
+ * 2. **`affected.count` and `affected.sampleRows`.** Up to 10 rows, each
32200
+ * identifying itself with a `term_id` column and the declaring sort's
32201
+ * `@key` features, so a confirm dialog can say WHICH rows go, not just how
32202
+ * many. Counted by running a derived read-only `MATCH` against a clone of
32203
+ * the tenant. Verified: four destructive previews (`RETRACT`, `CLEAR FACTS`,
32204
+ * `DROP SORT`, `DEFINE`) left the tenant's fact count and its lattice
32205
+ * unchanged.
32206
+ * 3. **`atomicRefusal`.** The same `drop_sort_in_atomic_program` verdict
32207
+ * `POST /api/v1/osfql` would refuse the program with, answered before the
32208
+ * program runs.
32209
+ *
32210
+ * ⚠️ **The one thing a caller must NOT do: treat `affected.count` as the
32211
+ * number of rows the run will remove.** It is the reach of the statement's
32212
+ * PATTERN. For `MATCH customer(name: ?N, spend: ?S); IF ?S > 60 THEN RETRACT
32213
+ * customer(name: ?N);` over three customers of which one has `spend > 60`, the
32214
+ * nested `RETRACT` answers `count: 3` — the derived `MATCH customer(name: ?N)`
32215
+ * applies neither the `IF` condition nor the binding the earlier statement
32216
+ * gives `?N`. Present it as "up to N rows" for a nested statement or one that
32217
+ * reads a variable from an earlier statement.
32218
+ *
32219
+ * Two further measured facts. The top-level `mutates` DOES fold nested
32220
+ * statements now: the `IF` program above answers `true`, and so does an `IF`
32221
+ * whose only writing branch is the `ELSE` — the earlier defect where an `IF`
32222
+ * with a writing branch answered `false` is fixed. And `affected` being absent
32223
+ * is ambiguous: `RETRACT customer(tier: "bronze")` matching zero rows omits
32224
+ * the field entirely rather than answering `count: 0`, exactly as a read does.
32225
+ *
32226
+ * `statement.index` is a single counter over the whole program: an `IF` at
32227
+ * index 1 carries nested entries at index 2 and 3.
32228
+ *
32229
+ * Request serialization matches {@link execute} — `query` plus the optional
32230
+ * `atomic` / `reactive` / `max_rows` / `timeout_ms` keys. The program TEXT is
32231
+ * a string value, so the request bridge's snake_case pass does not touch it;
32232
+ * a `camelCase` feature name inside the query survives verbatim. Sample-row
32233
+ * KEYS are data and are not camelCased on the way back.
32234
+ *
32235
+ * @example
32236
+ * ```typescript
32237
+ * const preview = await client.osfql.preview(
32238
+ * 'MATCH customer(name: ?N); RETRACT customer(tier: "gold");'
32239
+ * );
32240
+ *
32241
+ * console.log(preview.mutates); // true
32242
+ * const writes = preview.statements.filter((s) => s.mutates);
32243
+ * console.log(writes[0].id); // "retract"
32244
+ * console.log(writes[0].risk); // "targeted_destructive"
32245
+ * console.log(writes[0].affected?.count); // 2
32246
+ * console.log(writes[0].affected?.sampleRows); // [{ term_id: …, tier: … }, …]
32247
+ *
32248
+ * // A DROP SORT cannot share an atomic program
32249
+ * const refused = await client.osfql.preview('DROP SORT widget; INSERT widget(label: "x");');
32250
+ * console.log(refused.atomicRefusal?.code); // "drop_sort_in_atomic_program"
32251
+ *
32252
+ * // Ask the same question without the atomic constraint
32253
+ * const loose = await client.osfql.preview(
32254
+ * 'DROP SORT widget; INSERT widget(label: "x");',
32255
+ * { atomic: false },
32256
+ * );
32257
+ * console.log(loose.atomicRefusal); // undefined
32258
+ * ```
32259
+ */
32260
+ async preview(query, options, requestOptions) {
32261
+ const response = await this.api.previewOsfql(
32262
+ OsfqlRequestFromFrontToApi({ ...options, query }),
32263
+ toRequestParams(requestOptions)
32264
+ );
32265
+ const parsed = OsfqlPreviewResponseFromApiToFront(response.data);
32266
+ if (parsed === void 0) {
32267
+ throw new ReasoningLayerError(
32268
+ "osfql/preview returned an affected sample row that does not match the published OsfqlValue contract (expected tagged values with a lowercase `type` discriminator)"
32269
+ );
32270
+ }
32271
+ return parsed;
32272
+ }
31626
32273
  /**
31627
32274
  * Diagnose an OSFQL program for contradictions and inconsistencies.
31628
32275
  *
@@ -35440,38 +36087,6 @@ var SolverClient = class {
35440
36087
  }
35441
36088
  };
35442
36089
 
35443
- // src/normalizers/ontology-alignment.ts
35444
- function AlignOntologyRequestFromFrontToApi(model) {
35445
- return {
35446
- domain_owl: model.domainOwl,
35447
- targets: model.targets
35448
- };
35449
- }
35450
- function AlignmentMatchDtoFromApiToFront(dto) {
35451
- return {
35452
- domainSort: dto.domain_sort,
35453
- matchType: dto.match_type,
35454
- targetCurie: dto.target_curie,
35455
- targetLabel: dto.target_label
35456
- };
35457
- }
35458
- function AlignmentConflictDtoFromApiToFront(dto) {
35459
- return {
35460
- domainSort: dto.domain_sort,
35461
- targetA: dto.target_a,
35462
- targetB: dto.target_b
35463
- };
35464
- }
35465
- function AlignOntologyResponseFromApiToFront(dto) {
35466
- return {
35467
- conflicts: dto.conflicts.map(AlignmentConflictDtoFromApiToFront),
35468
- domainSorts: dto.domain_sorts,
35469
- mappingTtl: dto.mapping_ttl,
35470
- matches: dto.matches.map(AlignmentMatchDtoFromApiToFront),
35471
- targetSorts: dto.target_sorts
35472
- };
35473
- }
35474
-
35475
36090
  // src/resources/ontology-alignment.ts
35476
36091
  var OntologyAlignmentClient = class {
35477
36092
  /** @internal */
@@ -38527,15 +39142,15 @@ function TemporalSeriesPointFromApiToFront(value) {
38527
39142
  }
38528
39143
  const bucketStart = asNumber(value["bucket_start"]);
38529
39144
  const count = asNumber(value["count"]);
38530
- const aggregate = asNumber(value["aggregate"]);
38531
- if (bucketStart === void 0 || count === void 0 || aggregate === void 0) {
39145
+ const aggregate2 = asNumber(value["aggregate"]);
39146
+ if (bucketStart === void 0 || count === void 0 || aggregate2 === void 0) {
38532
39147
  return void 0;
38533
39148
  }
38534
39149
  return {
38535
39150
  bucketStart,
38536
39151
  bucketEnd: asNumber(value["bucket_end"]),
38537
39152
  count,
38538
- aggregate,
39153
+ aggregate: aggregate2,
38539
39154
  aggregateSecondary: asNumber(value["aggregate_secondary"])
38540
39155
  };
38541
39156
  }
@@ -42016,21 +42631,75 @@ var embeddings_exports = {};
42016
42631
  // src/builders/value.ts
42017
42632
  var Value = {
42018
42633
  /**
42019
- * Create a reference to another term by UUID.
42634
+ * Create a reference to another stored term by its UUID, or by the `@key`
42635
+ * values that name it.
42020
42636
  *
42021
- * @param id - The UUID of the referenced term.
42022
- * @returns A tagged `ReferenceValue`: `{"type": "Reference", "value": "uuid"}`.
42637
+ * @param target - The referenced term's UUID, or a {@link ReferenceDesignator}
42638
+ * naming it through the `@key` features of its sort.
42639
+ * @returns A tagged `ReferenceValue`: `{"type": "Reference", "value": "uuid"}`
42640
+ * for the id form, `{"type":"Reference","value":{"sort_name":…,"features":…}}`
42641
+ * for the designator form.
42642
+ * @throws {@link ValidationError} when the designator carries an empty
42643
+ * `sortName`, or names no feature — the engine refuses both, so the builder
42644
+ * refuses them before the round trip.
42023
42645
  *
42024
42646
  * @remarks
42025
- * Serialization format: Tagged (ValueDto). Use with term CRUD, queries, fuzzy operations.
42647
+ * **Serialization format: Tagged (`ValueDto`).** Use with term CRUD, queries
42648
+ * and fuzzy operations. The homoiconic inference endpoints take the untagged
42649
+ * format and have no designator form.
42650
+ *
42651
+ * The two forms differ in what they can be used for:
42652
+ *
42653
+ * - The **UUID form** is what a read answers, and the only form that can
42654
+ * point at a term whose sort declares no `@key`.
42655
+ * - The **designator form** is WRITE-side only. It RESOLVES to a term that
42656
+ * already exists and never mints one: measured on 2026-09-18, writing
42657
+ * a `payment` whose `invoice` feature was
42658
+ * `Value.reference({ sortName: 'invoice', features: { number: { type: 'String', value: 'INV-1' } } })`
42659
+ * stored `{"type":"Reference","value":"4641cbcb-…"}` — the id of the single
42660
+ * existing invoice — and the invoice extent still held exactly one term. A
42661
+ * designator matching nothing is refused `422 no term of sort 'invoice'
42662
+ * carries @key 'number' = "INV-NOPE"; a reference designator names an
42663
+ * existing term, it never creates one`. The same refusal arrives from
42664
+ * `POST /api/v1/terms/bulk` as a `BulkRefusedError` naming the entry index.
42665
+ *
42666
+ * The designator's `features` keys are OSF feature names — user data, not
42667
+ * schema — and survive the request bridge verbatim, so a feature declared
42668
+ * `invoiceNumber` must be spelled `invoiceNumber` here. Sending the
42669
+ * snake_cased spelling is refused: `422 feature 'invoice_number' of sort
42670
+ * 'invoice' is not a @key feature; a designator addresses a term only through
42671
+ * its @key`.
42026
42672
  *
42027
42673
  * @example
42028
42674
  * ```typescript
42675
+ * // By id:
42029
42676
  * Value.reference("550e8400-e29b-41d4-a716-446655440000")
42677
+ *
42678
+ * // By @key, when the caller has the business key and not the UUID:
42679
+ * Value.reference({
42680
+ * sortName: 'invoice',
42681
+ * features: { number: { type: 'String', value: 'INV-1' } },
42682
+ * })
42030
42683
  * ```
42031
42684
  */
42032
- reference(id) {
42033
- return { type: "Reference", value: id };
42685
+ reference(target) {
42686
+ if (typeof target === "string") {
42687
+ return { type: "Reference", value: target };
42688
+ }
42689
+ if (target.sortName.length === 0) {
42690
+ throw new ValidationError(
42691
+ "Value.reference() expects a designator with a non-empty sortName"
42692
+ );
42693
+ }
42694
+ if (Object.keys(target.features).length === 0) {
42695
+ throw new ValidationError(
42696
+ `Value.reference() expects a designator naming at least one @key feature of sort "${target.sortName}"; the engine refuses an empty designator`
42697
+ );
42698
+ }
42699
+ return {
42700
+ type: "Reference",
42701
+ value: { sortName: target.sortName, features: target.features }
42702
+ };
42034
42703
  },
42035
42704
  /**
42036
42705
  * Create a reference to a sort by UUID.
@@ -42769,6 +43438,7 @@ var FuzzyShape = {
42769
43438
  };
42770
43439
 
42771
43440
  // src/builders/psi.ts
43441
+ var NEGATION_SORT = "negation";
42772
43442
  function psi(sortOrName, features) {
42773
43443
  if (typeof sortOrName === "string") {
42774
43444
  if (!features) {
@@ -42797,6 +43467,38 @@ function bind(name, term) {
42797
43467
  }
42798
43468
  return { ...term, binding: name };
42799
43469
  }
43470
+ function not(clause) {
43471
+ if ("sortName" in clause && clause.sortName === NEGATION_SORT) {
43472
+ throw new ValidationError(
43473
+ `not() cannot negate a negation: the chainers read the "${NEGATION_SORT}" carrier one clause deep, so a nested negation is accepted and derives nothing`
43474
+ );
43475
+ }
43476
+ return {
43477
+ __psiTerm: true,
43478
+ sortName: NEGATION_SORT,
43479
+ features: { clause }
43480
+ };
43481
+ }
43482
+ function aggregate(spec) {
43483
+ if (spec.groupBy.length === 0) {
43484
+ throw new ValidationError(
43485
+ "aggregate() expects at least one groupBy feature; an aggregator with no group key has no group to fold into"
43486
+ );
43487
+ }
43488
+ const blank = spec.groupBy.find((name) => name.trim().length === 0);
43489
+ if (blank !== void 0) {
43490
+ throw new ValidationError("aggregate() expects every groupBy entry to be a feature name");
43491
+ }
43492
+ if (spec.target.trim().length === 0) {
43493
+ throw new ValidationError("aggregate() expects a target feature name");
43494
+ }
43495
+ if (spec.groupBy.includes(spec.target)) {
43496
+ throw new ValidationError(
43497
+ `aggregate() cannot aggregate "${spec.target}" and also group by it`
43498
+ );
43499
+ }
43500
+ return { groupBy: [...spec.groupBy], op: spec.op, target: spec.target };
43501
+ }
42800
43502
  function constrained(name, constraint) {
42801
43503
  return { __constrainedVar: true, name, constraint };
42802
43504
  }
@@ -43468,6 +44170,6 @@ var Flow = {
43468
44170
  }
43469
44171
  };
43470
44172
 
43471
- export { ANY_ROLE, action_reviews_exports as ActionReviews, actions_exports as Actions, admin_exports as Admin, agui_exports as Agui, analysis_exports as Analysis, anonymization_exports as Anonymization, ApiError, audit_exports as Audit, AuthenticationError, authz_exports as Authz, BadRequestError, batch_exports as Batch, cdl_exports as CDL, causal_exports as Causal, chase_exports as Chase, cognitive_exports as Cognitive, coherence_exports as Coherence, collections_exports as Collections, communities_exports as Communities, compliance_exports as Compliance, compliance_markings_exports as ComplianceMarkings, conformal_exports as Conformal, conformance_exports as Conformance, connectors_exports as Connectors, Constraint, ConstraintViolationError, constraints_exports as Constraints, control_exports as Control, conversation_exports as Conversation, corpus_exports as Corpus, dl_exports as DL, demo_exports as Demo, discovery_exports as Discovery, document_check_exports as DocumentCheck, documents_exports as Documents, embeddings_exports as Embeddings, execution_exports as Execution, extract_exports as Extract, feasibility_exports as Feasibility, Flow, flow_networks_exports as FlowNetworks, ForbiddenError, forecast_exports as Forecast, functions_exports as Functions, fuzzy_exports as Fuzzy, FuzzyShape, generation_exports as Generation, Geometry, guardrail_exports as Guardrail, health_exports as Health, homoiconic_exports as Homoiconic, ilp_exports as ILP, image_extraction_exports as ImageExtraction, inference_exports as Inference, ingestion_exports as Ingestion, IngestionFailedError, IngestionSession, InternalServerError, LP, ltn_exports as LTN, marketplace_exports as Marketplace, namespaces_exports as Namespaces, NetworkError, neuro_symbolic_exports as NeuroSymbolic, NotFoundError, ontology_exports as Ontology, ontology_alignment_exports as OntologyAlignment, ontology_bridge_exports as OntologyBridge, ontology_export_exports as OntologyExport, ontology_facade_exports as OntologyFacade, operations_exports as Operations, optimize_exports as Optimize, osf_diff_exports as OsfDiff, osfql_exports as Osfql, oversight_exports as Oversight, plain_values_exports as PlainValues, preferences_exports as Preferences, proof_engine_exports as ProofEngine, property_graph_exports as PropertyGraph, query_exports as Query, rag_exports as RAG, RateLimitError, reasoning_exports as Reasoning, ReasoningLayerClient, ReasoningLayerError, research_exports as Research, reviews_exports as Reviews, reward_exports as Reward, row_exports as Row, SDK_VERSION, sat_exports as Sat, scenarios_exports as Scenarios, scheduling_exports as Scheduling, smt_exports as Smt, snapshots_exports as Snapshots, solver_exports as Solver, SortBuilder, sorts_exports as Sorts, sources_exports as Sources, spaces_exports as Spaces, sparql_exports as Sparql, speakers_exports as Speakers, speech_exports as Speech, statistical_exports as Statistical, streaming_exports as Streaming, subscriptions_exports as Subscriptions, synthetic_exports as Synthetic, temporal_exports as Temporal, terms_exports as Terms, thomas_exports as Thomas, TimeoutError, translation_exports as Translation, ui_exports as UI, utilities_exports as Utilities, ValidationError, Value, values_exports as Values, verification_exports as Verification, vision_exports as Vision, visualization_exports as Visualization, WebSocketClient, WebSocketConnection, webhook_actions_exports as WebhookActions, allen, bind, collect, constrained, discriminateFeatureValue, guard, isConstrainedPlainVar, isPsiTermInput, isTaggedValueDto, isUuid, paginateByOffset, paginateByPage, psi, toTaggedFeatures, toTaggedValue, toTermInputDto, toUntaggedFeatures, toUntaggedValue };
44173
+ export { ANY_ROLE, action_reviews_exports as ActionReviews, actions_exports as Actions, admin_exports as Admin, agui_exports as Agui, analysis_exports as Analysis, anonymization_exports as Anonymization, ApiError, audit_exports as Audit, AuthenticationError, authz_exports as Authz, BadRequestError, batch_exports as Batch, BulkRefusedError, cdl_exports as CDL, causal_exports as Causal, chase_exports as Chase, cognitive_exports as Cognitive, coherence_exports as Coherence, collections_exports as Collections, communities_exports as Communities, compliance_exports as Compliance, compliance_markings_exports as ComplianceMarkings, conformal_exports as Conformal, conformance_exports as Conformance, connectors_exports as Connectors, Constraint, ConstraintViolationError, constraints_exports as Constraints, control_exports as Control, conversation_exports as Conversation, corpus_exports as Corpus, dl_exports as DL, demo_exports as Demo, discovery_exports as Discovery, document_check_exports as DocumentCheck, documents_exports as Documents, embeddings_exports as Embeddings, execution_exports as Execution, extract_exports as Extract, feasibility_exports as Feasibility, Flow, flow_networks_exports as FlowNetworks, ForbiddenError, forecast_exports as Forecast, functions_exports as Functions, fuzzy_exports as Fuzzy, FuzzyShape, generation_exports as Generation, Geometry, guardrail_exports as Guardrail, health_exports as Health, homoiconic_exports as Homoiconic, ilp_exports as ILP, image_extraction_exports as ImageExtraction, inference_exports as Inference, ingestion_exports as Ingestion, IngestionFailedError, IngestionSession, InternalServerError, LP, ltn_exports as LTN, marketplace_exports as Marketplace, namespaces_exports as Namespaces, NetworkError, neuro_symbolic_exports as NeuroSymbolic, NotFoundError, ontology_exports as Ontology, ontology_alignment_exports as OntologyAlignment, ontology_bridge_exports as OntologyBridge, ontology_export_exports as OntologyExport, ontology_facade_exports as OntologyFacade, operations_exports as Operations, optimize_exports as Optimize, osf_diff_exports as OsfDiff, osfql_exports as Osfql, oversight_exports as Oversight, plain_values_exports as PlainValues, preferences_exports as Preferences, proof_engine_exports as ProofEngine, property_graph_exports as PropertyGraph, query_exports as Query, rag_exports as RAG, RateLimitError, reasoning_exports as Reasoning, ReasoningLayerClient, ReasoningLayerError, research_exports as Research, reviews_exports as Reviews, reward_exports as Reward, row_exports as Row, SDK_VERSION, sat_exports as Sat, scenarios_exports as Scenarios, scheduling_exports as Scheduling, smt_exports as Smt, snapshots_exports as Snapshots, solver_exports as Solver, SortBuilder, sorts_exports as Sorts, sources_exports as Sources, spaces_exports as Spaces, sparql_exports as Sparql, speakers_exports as Speakers, speech_exports as Speech, statistical_exports as Statistical, streaming_exports as Streaming, subscriptions_exports as Subscriptions, synthetic_exports as Synthetic, temporal_exports as Temporal, terms_exports as Terms, thomas_exports as Thomas, TimeoutError, translation_exports as Translation, ui_exports as UI, utilities_exports as Utilities, ValidationError, Value, values_exports as Values, verification_exports as Verification, vision_exports as Vision, visualization_exports as Visualization, WebSocketClient, WebSocketConnection, webhook_actions_exports as WebhookActions, aggregate, allen, bind, collect, constrained, discriminateFeatureValue, guard, isConstrainedPlainVar, isPsiTermInput, isTaggedValueDto, isUuid, not, paginateByOffset, paginateByPage, psi, toTaggedFeatures, toTaggedValue, toTermInputDto, toUntaggedFeatures, toUntaggedValue };
43472
44174
  //# sourceMappingURL=index.js.map
43473
44175
  //# sourceMappingURL=index.js.map