@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.cjs +813 -108
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2761 -729
- package/dist/index.d.ts +2761 -729
- package/dist/index.js +811 -109
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7,7 +7,7 @@ var __export = (target, all) => {
|
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
// src/config.ts
|
|
10
|
-
var SDK_VERSION = "1.
|
|
10
|
+
var SDK_VERSION = "1.28.0";
|
|
11
11
|
function resolveConfig(config) {
|
|
12
12
|
if (!config.baseUrl) {
|
|
13
13
|
throw new Error("ClientConfig.baseUrl is required");
|
|
@@ -102,6 +102,20 @@ var ConstraintViolationError = class extends ApiError {
|
|
|
102
102
|
this.constraint = constraint;
|
|
103
103
|
}
|
|
104
104
|
};
|
|
105
|
+
var BulkRefusedError = class extends ApiError {
|
|
106
|
+
name = "BulkRefusedError";
|
|
107
|
+
/**
|
|
108
|
+
* The refused rows, each naming its index in the request's `terms` array.
|
|
109
|
+
*
|
|
110
|
+
* @remarks
|
|
111
|
+
* Never empty — the engine answers this shape only when it refused something.
|
|
112
|
+
*/
|
|
113
|
+
rows;
|
|
114
|
+
constructor(message, body, headers, rows, errorCode) {
|
|
115
|
+
super(message, 422, body, headers, errorCode);
|
|
116
|
+
this.rows = rows;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
105
119
|
var RateLimitError = class extends ApiError {
|
|
106
120
|
name = "RateLimitError";
|
|
107
121
|
/** Seconds to wait before retrying, or null if not specified. */
|
|
@@ -191,6 +205,13 @@ function createApiError(status, body, headers) {
|
|
|
191
205
|
constraint
|
|
192
206
|
);
|
|
193
207
|
}
|
|
208
|
+
case 422: {
|
|
209
|
+
const rows = bulkRefusalRows(body);
|
|
210
|
+
if (rows !== null) {
|
|
211
|
+
return new BulkRefusedError(message, body, headers, rows, errorCode);
|
|
212
|
+
}
|
|
213
|
+
return new ApiError(message, status, body, headers, errorCode);
|
|
214
|
+
}
|
|
194
215
|
case 429: {
|
|
195
216
|
const rl = parseRateLimitHeaders(headers);
|
|
196
217
|
return new RateLimitError(message, body, headers, errorCode, rl.retryAfter, rl.limit, rl.remaining);
|
|
@@ -207,6 +228,23 @@ function isErrorBody(body) {
|
|
|
207
228
|
const obj = body;
|
|
208
229
|
return typeof obj.error === "string" && typeof obj.message === "string";
|
|
209
230
|
}
|
|
231
|
+
function bulkRefusalRows(body) {
|
|
232
|
+
if (typeof body !== "object" || body === null) return null;
|
|
233
|
+
const obj = body;
|
|
234
|
+
if (!Array.isArray(obj.errors)) return null;
|
|
235
|
+
const rows = [];
|
|
236
|
+
for (const entry of obj.errors) {
|
|
237
|
+
if (typeof entry !== "object" || entry === null) return null;
|
|
238
|
+
const row = entry;
|
|
239
|
+
if (typeof row.index !== "number" || typeof row.message !== "string") return null;
|
|
240
|
+
rows.push({
|
|
241
|
+
index: row.index,
|
|
242
|
+
message: row.message,
|
|
243
|
+
...typeof row.feature === "string" ? { feature: row.feature } : {}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return rows.length > 0 ? rows : null;
|
|
247
|
+
}
|
|
210
248
|
function isConstraintViolationBody(body) {
|
|
211
249
|
if (!isErrorBody(body)) return false;
|
|
212
250
|
if (!("details" in body)) return false;
|
|
@@ -1641,7 +1679,7 @@ var Terms = class {
|
|
|
1641
1679
|
...params
|
|
1642
1680
|
});
|
|
1643
1681
|
/**
|
|
1644
|
-
* @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.
|
|
1682
|
+
* @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.
|
|
1645
1683
|
*
|
|
1646
1684
|
* @tags terms
|
|
1647
1685
|
* @name BulkAddTerms
|
|
@@ -1939,7 +1977,7 @@ var Inference = class {
|
|
|
1939
1977
|
...params
|
|
1940
1978
|
});
|
|
1941
1979
|
/**
|
|
1942
|
-
* @description This drops the hydrated base facts, the forward-chain `
|
|
1980
|
+
* @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.
|
|
1943
1981
|
*
|
|
1944
1982
|
* @tags inference
|
|
1945
1983
|
* @name ClearFacts
|
|
@@ -6681,7 +6719,7 @@ var Query = class {
|
|
|
6681
6719
|
this.http = http;
|
|
6682
6720
|
}
|
|
6683
6721
|
/**
|
|
6684
|
-
* @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.
|
|
6722
|
+
* @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.
|
|
6685
6723
|
*
|
|
6686
6724
|
* @tags query
|
|
6687
6725
|
* @name FindBySort
|
|
@@ -9594,7 +9632,7 @@ var Admin = class {
|
|
|
9594
9632
|
...params
|
|
9595
9633
|
});
|
|
9596
9634
|
/**
|
|
9597
|
-
* @description
|
|
9635
|
+
* @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'.
|
|
9598
9636
|
*
|
|
9599
9637
|
* @tags admin
|
|
9600
9638
|
* @name RebuildDerivedFacts
|
|
@@ -10040,6 +10078,24 @@ var Osfql = class {
|
|
|
10040
10078
|
format: "json",
|
|
10041
10079
|
...params
|
|
10042
10080
|
});
|
|
10081
|
+
/**
|
|
10082
|
+
* @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\");" } ```
|
|
10083
|
+
*
|
|
10084
|
+
* @tags osfql
|
|
10085
|
+
* @name PreviewOsfql
|
|
10086
|
+
* @summary Preview an OSFQL program: what each statement would do, decided without running anything (#257).
|
|
10087
|
+
* @request POST:/api/v1/osfql/preview
|
|
10088
|
+
* @secure
|
|
10089
|
+
*/
|
|
10090
|
+
previewOsfql = (data, params = {}) => this.http.request({
|
|
10091
|
+
path: `/api/v1/osfql/preview`,
|
|
10092
|
+
method: "POST",
|
|
10093
|
+
body: data,
|
|
10094
|
+
secure: true,
|
|
10095
|
+
type: "application/json",
|
|
10096
|
+
format: "json",
|
|
10097
|
+
...params
|
|
10098
|
+
});
|
|
10043
10099
|
};
|
|
10044
10100
|
|
|
10045
10101
|
// src/api-spec/generated/Context.ts
|
|
@@ -13229,6 +13285,45 @@ var Research = class {
|
|
|
13229
13285
|
});
|
|
13230
13286
|
};
|
|
13231
13287
|
|
|
13288
|
+
// src/normalizers/ontology-alignment.ts
|
|
13289
|
+
function AlignOntologyRequestFromFrontToApi(model) {
|
|
13290
|
+
return {
|
|
13291
|
+
domain_owl: model.domainOwl,
|
|
13292
|
+
targets: model.targets
|
|
13293
|
+
};
|
|
13294
|
+
}
|
|
13295
|
+
function AlignmentMatchDtoFromApiToFront(dto) {
|
|
13296
|
+
return {
|
|
13297
|
+
domainSort: dto.domain_sort,
|
|
13298
|
+
matchType: dto.match_type,
|
|
13299
|
+
targetCurie: dto.target_curie,
|
|
13300
|
+
targetLabel: dto.target_label
|
|
13301
|
+
};
|
|
13302
|
+
}
|
|
13303
|
+
function AlignmentConflictDtoFromApiToFront(dto) {
|
|
13304
|
+
return {
|
|
13305
|
+
domainSort: dto.domain_sort,
|
|
13306
|
+
targetA: dto.target_a,
|
|
13307
|
+
targetB: dto.target_b
|
|
13308
|
+
};
|
|
13309
|
+
}
|
|
13310
|
+
function ExternalMatchDtoFromApiToFront(dto) {
|
|
13311
|
+
return {
|
|
13312
|
+
matchType: dto.match_type,
|
|
13313
|
+
ontologyId: dto.ontology_id,
|
|
13314
|
+
source: dto.source
|
|
13315
|
+
};
|
|
13316
|
+
}
|
|
13317
|
+
function AlignOntologyResponseFromApiToFront(dto) {
|
|
13318
|
+
return {
|
|
13319
|
+
conflicts: dto.conflicts.map(AlignmentConflictDtoFromApiToFront),
|
|
13320
|
+
domainSorts: dto.domain_sorts,
|
|
13321
|
+
mappingTtl: dto.mapping_ttl,
|
|
13322
|
+
matches: dto.matches.map(AlignmentMatchDtoFromApiToFront),
|
|
13323
|
+
targetSorts: dto.target_sorts
|
|
13324
|
+
};
|
|
13325
|
+
}
|
|
13326
|
+
|
|
13232
13327
|
// src/utils/records.ts
|
|
13233
13328
|
function definedEntries(map) {
|
|
13234
13329
|
return Object.entries(map).filter((entry) => entry[1] !== void 0);
|
|
@@ -13280,7 +13375,12 @@ function FeatureDescriptorDtoFromApiToFront(dto) {
|
|
|
13280
13375
|
// `undefined` for a question the wire already answers.
|
|
13281
13376
|
required: dto.required ?? false,
|
|
13282
13377
|
constraint: dto.constraint ? ConstraintDtoFromApiToFront(dto.constraint) : void 0,
|
|
13283
|
-
key: dto.key
|
|
13378
|
+
key: dto.key,
|
|
13379
|
+
expectedSortName: dto.expected_sort_name ?? void 0,
|
|
13380
|
+
minCount: dto.min_count ?? void 0,
|
|
13381
|
+
maxCount: dto.max_count ?? void 0,
|
|
13382
|
+
cardinalityOrigin: dto.cardinality_origin ?? void 0,
|
|
13383
|
+
annotations: definedRecord2(dto.annotations)
|
|
13284
13384
|
};
|
|
13285
13385
|
}
|
|
13286
13386
|
function FeatureDescriptorDtoFromFrontToApi(model) {
|
|
@@ -13290,7 +13390,18 @@ function FeatureDescriptorDtoFromFrontToApi(model) {
|
|
|
13290
13390
|
expected_type_hint: model.expectedTypeHint ?? void 0,
|
|
13291
13391
|
required: model.required,
|
|
13292
13392
|
constraint: model.constraint ? ConstraintDtoFromFrontToApi(model.constraint) : void 0,
|
|
13293
|
-
key: model.key
|
|
13393
|
+
key: model.key,
|
|
13394
|
+
expected_sort_name: model.expectedSortName ?? void 0,
|
|
13395
|
+
min_count: model.minCount ?? void 0,
|
|
13396
|
+
max_count: model.maxCount ?? void 0,
|
|
13397
|
+
cardinality_origin: model.cardinalityOrigin ?? void 0,
|
|
13398
|
+
annotations: model.annotations
|
|
13399
|
+
};
|
|
13400
|
+
}
|
|
13401
|
+
function CoextensiveDefinitionDtoFromApiToFront(dto) {
|
|
13402
|
+
return {
|
|
13403
|
+
definition: dto.definition,
|
|
13404
|
+
exampleCount: dto.example_count
|
|
13294
13405
|
};
|
|
13295
13406
|
}
|
|
13296
13407
|
function BoundConstraintDtoFromApiToFront(dto) {
|
|
@@ -13372,7 +13483,17 @@ function SortDtoFromApiToFront(dto) {
|
|
|
13372
13483
|
worldMode: dto.world_mode,
|
|
13373
13484
|
annotations: definedRecord2(dto.annotations),
|
|
13374
13485
|
pluginId: dto.plugin_id ?? void 0,
|
|
13375
|
-
pluginLocalName: dto.plugin_local_name ?? void 0
|
|
13486
|
+
pluginLocalName: dto.plugin_local_name ?? void 0,
|
|
13487
|
+
altLabels: dto.alt_labels,
|
|
13488
|
+
hiddenLabels: dto.hidden_labels,
|
|
13489
|
+
scopeNote: dto.scope_note ?? void 0,
|
|
13490
|
+
related: dto.related,
|
|
13491
|
+
definition: dto.definition ?? void 0,
|
|
13492
|
+
coextensive: dto.coextensive?.map(CoextensiveDefinitionDtoFromApiToFront),
|
|
13493
|
+
externalMatches: dto.external_matches?.map(
|
|
13494
|
+
(match) => ExternalMatchDtoFromApiToFront(match)
|
|
13495
|
+
),
|
|
13496
|
+
featureEquations: dto.feature_equations
|
|
13376
13497
|
};
|
|
13377
13498
|
}
|
|
13378
13499
|
function SortInfoDtoFromApiToFront(dto) {
|
|
@@ -13384,7 +13505,20 @@ function SortInfoDtoFromApiToFront(dto) {
|
|
|
13384
13505
|
function SortListResponseFromApiToFront(dto) {
|
|
13385
13506
|
return {
|
|
13386
13507
|
sorts: dto.sorts.map(SortDtoFromApiToFront),
|
|
13387
|
-
count: dto.count
|
|
13508
|
+
count: dto.count,
|
|
13509
|
+
total: dto.total,
|
|
13510
|
+
offset: dto.offset
|
|
13511
|
+
};
|
|
13512
|
+
}
|
|
13513
|
+
function ListSortsQueryFromFrontToApi(query) {
|
|
13514
|
+
if (query === void 0) return void 0;
|
|
13515
|
+
return {
|
|
13516
|
+
include_system: query.includeSystem,
|
|
13517
|
+
limit: query.limit,
|
|
13518
|
+
llm_extracted: query.llmExtracted,
|
|
13519
|
+
name_prefix: query.namePrefix?.join(","),
|
|
13520
|
+
needs_review: query.needsReview,
|
|
13521
|
+
offset: query.offset
|
|
13388
13522
|
};
|
|
13389
13523
|
}
|
|
13390
13524
|
function CreateSortRequestFromFrontToApi(model) {
|
|
@@ -13406,6 +13540,8 @@ function BulkSortDefinitionFromFrontToApi(model) {
|
|
|
13406
13540
|
parents: model.parents,
|
|
13407
13541
|
features: model.features?.map(FeatureDescriptorDtoFromFrontToApi),
|
|
13408
13542
|
alt_labels: model.altLabels,
|
|
13543
|
+
hidden_labels: model.hiddenLabels,
|
|
13544
|
+
scope_note: model.scopeNote ?? void 0,
|
|
13409
13545
|
description: model.description ?? void 0,
|
|
13410
13546
|
world_mode: model.worldMode
|
|
13411
13547
|
};
|
|
@@ -14015,13 +14151,60 @@ var SortsClient = class {
|
|
|
14015
14151
|
return response.data;
|
|
14016
14152
|
}
|
|
14017
14153
|
/**
|
|
14018
|
-
* List
|
|
14154
|
+
* List every sort the tenant owns.
|
|
14155
|
+
*
|
|
14156
|
+
* @param requestOptions - Per-call transport overrides.
|
|
14157
|
+
* @returns The sorts, each with its feature declarations.
|
|
14158
|
+
* @throws {ApiError} When the engine refuses the request.
|
|
14159
|
+
*
|
|
14160
|
+
* @remarks
|
|
14161
|
+
* This asks for the whole listing and keeps only the array. A production
|
|
14162
|
+
* tenant can own 1 M+ sorts (~500 MB uncompressed), which one response
|
|
14163
|
+
* cannot deliver — use {@link SortsClient.listSortsPage} to window it, to
|
|
14164
|
+
* filter it, or to read the `total` that says when to stop.
|
|
14019
14165
|
*
|
|
14020
|
-
* @
|
|
14166
|
+
* @example
|
|
14167
|
+
* ```typescript
|
|
14168
|
+
* const sorts = await client.sorts.listSorts();
|
|
14169
|
+
* ```
|
|
14021
14170
|
*/
|
|
14022
14171
|
async listSorts(requestOptions) {
|
|
14023
|
-
|
|
14024
|
-
|
|
14172
|
+
return (await this.listSortsPage(void 0, requestOptions)).sorts;
|
|
14173
|
+
}
|
|
14174
|
+
/**
|
|
14175
|
+
* List the tenant's sorts, keeping the envelope — `count`, `total` and
|
|
14176
|
+
* `offset` beside the page.
|
|
14177
|
+
*
|
|
14178
|
+
* @param query - The window and filters over the listing.
|
|
14179
|
+
* @param requestOptions - Per-call transport overrides.
|
|
14180
|
+
* @returns The page, its length, the tenant's filtered total and the
|
|
14181
|
+
* offset the page starts at.
|
|
14182
|
+
* @throws {ApiError} When the engine refuses the request.
|
|
14183
|
+
*
|
|
14184
|
+
* @remarks
|
|
14185
|
+
* `count` is the length of THIS page; `total` is what the tenant owns after
|
|
14186
|
+
* filters, across all pages. Measured against the engine on a tenant
|
|
14187
|
+
* holding three sorts, `GET /api/v1/sorts/tenant/{id}` answers
|
|
14188
|
+
* `{"count":3,"total":3,"offset":0}`.
|
|
14189
|
+
*
|
|
14190
|
+
* @example
|
|
14191
|
+
* ```typescript
|
|
14192
|
+
* let offset = 0;
|
|
14193
|
+
* for (;;) {
|
|
14194
|
+
* const page = await client.sorts.listSortsPage({ limit: 500, offset });
|
|
14195
|
+
* consume(page.sorts);
|
|
14196
|
+
* offset += page.count;
|
|
14197
|
+
* if (page.total === undefined || offset >= page.total) break;
|
|
14198
|
+
* }
|
|
14199
|
+
* ```
|
|
14200
|
+
*/
|
|
14201
|
+
async listSortsPage(query, requestOptions) {
|
|
14202
|
+
const response = await this.sorts.listSorts(
|
|
14203
|
+
this.tenantId,
|
|
14204
|
+
ListSortsQueryFromFrontToApi(query),
|
|
14205
|
+
toRequestParams(requestOptions)
|
|
14206
|
+
);
|
|
14207
|
+
return SortListResponseFromApiToFront(response.data);
|
|
14025
14208
|
}
|
|
14026
14209
|
/**
|
|
14027
14210
|
* Bulk-create sorts with name-based parent references.
|
|
@@ -14628,6 +14811,11 @@ function toUntaggedValue(value) {
|
|
|
14628
14811
|
return value.value.map(toUntaggedValue);
|
|
14629
14812
|
}
|
|
14630
14813
|
if (value.type === "Reference") {
|
|
14814
|
+
if (typeof value.value !== "string") {
|
|
14815
|
+
throw new ValidationError(
|
|
14816
|
+
`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.`
|
|
14817
|
+
);
|
|
14818
|
+
}
|
|
14631
14819
|
return { termId: value.value };
|
|
14632
14820
|
}
|
|
14633
14821
|
if (value.type === "SortId") {
|
|
@@ -14691,6 +14879,18 @@ function ValueDtoFromApiToFront(dto) {
|
|
|
14691
14879
|
value: dto.value.map(ValueDtoFromApiToFront)
|
|
14692
14880
|
};
|
|
14693
14881
|
}
|
|
14882
|
+
if (dto.type === "Reference") {
|
|
14883
|
+
if (typeof dto.value === "string") {
|
|
14884
|
+
return { type: "Reference", value: dto.value };
|
|
14885
|
+
}
|
|
14886
|
+
return {
|
|
14887
|
+
type: "Reference",
|
|
14888
|
+
value: {
|
|
14889
|
+
sortName: dto.value.sort_name,
|
|
14890
|
+
features: featuresFromApiToFront(dto.value.features)
|
|
14891
|
+
}
|
|
14892
|
+
};
|
|
14893
|
+
}
|
|
14694
14894
|
if (dto.type === "PsiTerm") {
|
|
14695
14895
|
const features = dto.value.features;
|
|
14696
14896
|
return {
|
|
@@ -14781,6 +14981,18 @@ function ValueDtoFromFrontToApi(value) {
|
|
|
14781
14981
|
value: value.value.map(ValueDtoFromFrontToApi)
|
|
14782
14982
|
};
|
|
14783
14983
|
}
|
|
14984
|
+
if (value.type === "Reference") {
|
|
14985
|
+
if (typeof value.value === "string") {
|
|
14986
|
+
return { type: "Reference", value: value.value };
|
|
14987
|
+
}
|
|
14988
|
+
return {
|
|
14989
|
+
type: "Reference",
|
|
14990
|
+
value: {
|
|
14991
|
+
sort_name: value.value.sortName,
|
|
14992
|
+
features: featuresFromFrontToApi(value.value.features)
|
|
14993
|
+
}
|
|
14994
|
+
};
|
|
14995
|
+
}
|
|
14784
14996
|
if (value.type === "PsiTerm") {
|
|
14785
14997
|
const features = value.value.features;
|
|
14786
14998
|
return {
|
|
@@ -14866,7 +15078,10 @@ function TermDtoFromApiToFront(dto) {
|
|
|
14866
15078
|
sortName: dto.sort_name ?? void 0,
|
|
14867
15079
|
displayName: dto.display_name ?? void 0,
|
|
14868
15080
|
referencedTerms,
|
|
14869
|
-
origin: dto.origin
|
|
15081
|
+
origin: dto.origin,
|
|
15082
|
+
// `derived_by` is a uuid or `null`; the shipped field is `string |
|
|
15083
|
+
// undefined`, so a null collapses rather than travelling as a third state.
|
|
15084
|
+
...dto.derived_by == null ? {} : { derivedBy: dto.derived_by }
|
|
14870
15085
|
};
|
|
14871
15086
|
}
|
|
14872
15087
|
function WitnessProofDtoFromApiToFront(dto) {
|
|
@@ -14895,9 +15110,21 @@ function CreateTermRequestFromFrontToApi(model) {
|
|
|
14895
15110
|
return {
|
|
14896
15111
|
sort_id: model.sortId,
|
|
14897
15112
|
owner_id: model.ownerId,
|
|
14898
|
-
features: featuresFromFrontToApi(model.features)
|
|
15113
|
+
features: featuresFromFrontToApi(model.features),
|
|
15114
|
+
...model.id === void 0 ? {} : { id: model.id }
|
|
14899
15115
|
};
|
|
14900
15116
|
}
|
|
15117
|
+
function CreateTermInputFromFrontToApi(model) {
|
|
15118
|
+
if ("sortName" in model) {
|
|
15119
|
+
return {
|
|
15120
|
+
sort_name: model.sortName,
|
|
15121
|
+
owner_id: model.ownerId,
|
|
15122
|
+
features: featuresFromFrontToApi(model.features),
|
|
15123
|
+
...model.id === void 0 ? {} : { id: model.id }
|
|
15124
|
+
};
|
|
15125
|
+
}
|
|
15126
|
+
return CreateTermRequestFromFrontToApi(model);
|
|
15127
|
+
}
|
|
14901
15128
|
function UpdateTermRequestFromFrontToApi(model) {
|
|
14902
15129
|
return {
|
|
14903
15130
|
features: featuresFromFrontToApi(model.features)
|
|
@@ -14905,15 +15132,34 @@ function UpdateTermRequestFromFrontToApi(model) {
|
|
|
14905
15132
|
}
|
|
14906
15133
|
function BulkAddTermsRequestFromFrontToApi(model) {
|
|
14907
15134
|
return {
|
|
14908
|
-
terms: model.terms.map(
|
|
15135
|
+
terms: model.terms.map(CreateTermInputFromFrontToApi),
|
|
15136
|
+
dry_run: model.dryRun,
|
|
15137
|
+
partial: model.partial
|
|
14909
15138
|
};
|
|
14910
15139
|
}
|
|
14911
15140
|
function BulkAddTermsResponseFromApiToFront(dto) {
|
|
14912
15141
|
return {
|
|
14913
|
-
|
|
15142
|
+
// `term_ids` is absent on a dry run and shorter than the request under
|
|
15143
|
+
// `partial`, so it is optional on the shipped type rather than defaulted to
|
|
15144
|
+
// an empty array — an empty array would read as "nothing was created", and
|
|
15145
|
+
// a dry run over a clean batch creates nothing while reporting a non-zero
|
|
15146
|
+
// `termsAdded`. The two facts are different and both matter.
|
|
15147
|
+
...dto.term_ids ? { termIds: dto.term_ids } : {},
|
|
15148
|
+
termsAdded: dto.terms_added,
|
|
15149
|
+
dryRun: dto.dry_run,
|
|
15150
|
+
...dto.refused === void 0 || dto.refused === null ? {} : { refused: dto.refused },
|
|
15151
|
+
...dto.errors ? { errors: dto.errors.map(BulkRowRefusalFromApiToFront) } : {},
|
|
15152
|
+
...dto.coreferenced_term_ids ? { coreferencedTermIds: dto.coreferenced_term_ids } : {},
|
|
14914
15153
|
processingTimeMs: dto.processing_time_ms
|
|
14915
15154
|
};
|
|
14916
15155
|
}
|
|
15156
|
+
function BulkRowRefusalFromApiToFront(dto) {
|
|
15157
|
+
return {
|
|
15158
|
+
index: dto.index,
|
|
15159
|
+
message: dto.message,
|
|
15160
|
+
...dto.feature ? { feature: dto.feature } : {}
|
|
15161
|
+
};
|
|
15162
|
+
}
|
|
14917
15163
|
function ClearTermsResponseFromApiToFront(dto) {
|
|
14918
15164
|
return {
|
|
14919
15165
|
message: dto.message,
|
|
@@ -14958,7 +15204,12 @@ function ValidatedUnifyResponseFromApiToFront(dto) {
|
|
|
14958
15204
|
function TermListResponseFromApiToFront(dto) {
|
|
14959
15205
|
return {
|
|
14960
15206
|
terms: dto.terms.map(TermDtoFromApiToFront),
|
|
14961
|
-
count: dto.count
|
|
15207
|
+
count: dto.count,
|
|
15208
|
+
// `total` is `null` on a route that does not page, and `note` is `null`
|
|
15209
|
+
// whenever the engine has no remark. Both collapse to absent so a consumer
|
|
15210
|
+
// tests one thing — presence — rather than two.
|
|
15211
|
+
...dto.total == null ? {} : { total: dto.total },
|
|
15212
|
+
...dto.note == null ? {} : { note: dto.note }
|
|
14962
15213
|
};
|
|
14963
15214
|
}
|
|
14964
15215
|
function TermReferrerDtoFromApiToFront(dto) {
|
|
@@ -15075,10 +15326,9 @@ var TermsClient = class {
|
|
|
15075
15326
|
* ```
|
|
15076
15327
|
*/
|
|
15077
15328
|
async createTerm(request, requestOptions) {
|
|
15078
|
-
const wireRequest =
|
|
15079
|
-
...request,
|
|
15080
|
-
|
|
15081
|
-
});
|
|
15329
|
+
const wireRequest = CreateTermInputFromFrontToApi(
|
|
15330
|
+
"sortName" in request ? { ...request, features: convertFeatures(request.features) } : { ...request, features: convertFeatures(request.features) }
|
|
15331
|
+
);
|
|
15082
15332
|
const response = await this.api.addTerm(wireRequest, toRequestParams(requestOptions));
|
|
15083
15333
|
return TermResponseFromApiToFront(response.data);
|
|
15084
15334
|
}
|
|
@@ -15253,13 +15503,91 @@ var TermsClient = class {
|
|
|
15253
15503
|
}
|
|
15254
15504
|
}
|
|
15255
15505
|
/**
|
|
15256
|
-
*
|
|
15506
|
+
* Create many terms in one request, all-or-nothing by default.
|
|
15257
15507
|
*
|
|
15258
|
-
* @param request -
|
|
15259
|
-
*
|
|
15508
|
+
* @param request - The rows, plus `dryRun` to check without writing and
|
|
15509
|
+
* `partial` to keep the rows that passed. Features may be plain JS values
|
|
15510
|
+
* or `Value.*` output.
|
|
15511
|
+
* @param requestOptions - Per-call request options.
|
|
15512
|
+
* @returns What was written (or, on a dry run, what WOULD be written):
|
|
15513
|
+
* `termsAdded`, `termIds` on a real write, `errors` beside them under
|
|
15514
|
+
* `partial`, `coreferencedTermIds` on a dry run.
|
|
15515
|
+
* @throws {@link BulkRefusedError} 422 when the batch was refused and
|
|
15516
|
+
* NOTHING was written. Read `error.rows` — one entry per refused row, each
|
|
15517
|
+
* naming its `index` in `request.terms`. This is the answer for a refused
|
|
15518
|
+
* default batch, for a refused dry run, and for a `partial` batch in which
|
|
15519
|
+
* every row was refused.
|
|
15520
|
+
* @throws {@link ApiError} 409 when the end-of-batch constraint propagation
|
|
15521
|
+
* refuses the batch as a whole. That verdict cannot be attributed to a row,
|
|
15522
|
+
* so `partial` does not split it.
|
|
15523
|
+
*
|
|
15524
|
+
* @remarks
|
|
15525
|
+
* **Serialization format: Tagged (`ValueDto`).** Plain feature values are
|
|
15526
|
+
* converted exactly as {@link TermsClient.createTerm} converts them.
|
|
15527
|
+
*
|
|
15528
|
+
* There are three outcomes, and they are distinguishable without reading a
|
|
15529
|
+
* status code:
|
|
15530
|
+
*
|
|
15531
|
+
* 1. **A clean write** — `201`. `termIds` holds one id per request row, in
|
|
15532
|
+
* request order; `termsAdded` equals its length; `errors` is absent.
|
|
15533
|
+
* Measured 2026-09-18, a 2-row clean batch:
|
|
15534
|
+
* `{"terms_added":2,"term_ids":["3d936f37-…","f4a77a0b-…"],
|
|
15535
|
+
* "processing_time_ms":38,"dry_run":false}`.
|
|
15536
|
+
* 2. **A partial write** — `201`, and only with `partial: true`. Some rows
|
|
15537
|
+
* landed. `termIds` holds one id per ACCEPTED row, so it is SHORTER than
|
|
15538
|
+
* `request.terms`, and `errors` sits beside it naming the refused ones.
|
|
15539
|
+
* Map a refusal back with `errors[].index`, never with a position in
|
|
15540
|
+
* `termIds`. Measured, a 2-row batch whose second row violates a declared
|
|
15541
|
+
* range:
|
|
15542
|
+
* `{"terms_added":1,"term_ids":["d6ba85e6-…"],"errors":[{"index":1,
|
|
15543
|
+
* "feature":"price","message":"Constraint violation: Feature 'price'
|
|
15544
|
+
* value violates its declared range/constraint"}],"refused":1,
|
|
15545
|
+
* "processing_time_ms":37,"dry_run":false}`.
|
|
15546
|
+
* 3. **A refusal** — `422`, thrown as {@link BulkRefusedError}. Nothing was
|
|
15547
|
+
* written. Measured, the same bad batch WITHOUT `partial`:
|
|
15548
|
+
* `{"code":"bulk_refused","message":"1 of 2 entries were refused; the
|
|
15549
|
+
* whole batch was refused and nothing was written","errors":[{"index":1,
|
|
15550
|
+
* "feature":"price","message":"Constraint violation: …"}]}`. With
|
|
15551
|
+
* `partial: true` and BOTH rows bad, the same `422`:
|
|
15552
|
+
* `"2 of 2 entries were refused; the whole batch was refused and nothing
|
|
15553
|
+
* was written"`.
|
|
15554
|
+
*
|
|
15555
|
+
* **`dryRun` runs every check and writes nothing**, and it answers in the
|
|
15556
|
+
* same two shapes. A clean dry run is `200` and reports NO `termIds` —
|
|
15557
|
+
* measured:
|
|
15558
|
+
* `{"terms_added":2,"coreferenced_term_ids":[],"processing_time_ms":0,
|
|
15559
|
+
* "dry_run":true}`. The absence is deliberate: the rollback discarded every
|
|
15560
|
+
* id it minted, a later real write mints different ones, so the vector would
|
|
15561
|
+
* name nothing. The ids that DO outlive a dry run are the existing entities
|
|
15562
|
+
* a `@key` coreference would have merged into, and they come back as
|
|
15563
|
+
* `coreferencedTermIds`. A dry run over a BAD batch throws
|
|
15564
|
+
* {@link BulkRefusedError} with the same per-row refusals a real write throws
|
|
15565
|
+
* — measured, identical body to outcome 3 — so a caller can validate an
|
|
15566
|
+
* import with one call and never touch the store.
|
|
15567
|
+
*
|
|
15568
|
+
* @example
|
|
15569
|
+
* ```typescript
|
|
15570
|
+
* // Validate an import without writing.
|
|
15571
|
+
* try {
|
|
15572
|
+
* const check = await client.terms.bulkCreateTerms({ terms: rows, dryRun: true });
|
|
15573
|
+
* console.log(`${check.termsAdded} rows would be written`); // no check.termIds
|
|
15574
|
+
* } catch (e) {
|
|
15575
|
+
* if (e instanceof BulkRefusedError) {
|
|
15576
|
+
* for (const row of e.rows) console.error(`row ${row.index}: ${row.message}`);
|
|
15577
|
+
* }
|
|
15578
|
+
* }
|
|
15579
|
+
*
|
|
15580
|
+
* // Write what passes, and report what did not.
|
|
15581
|
+
* const result = await client.terms.bulkCreateTerms({ terms: rows, partial: true });
|
|
15582
|
+
* console.log(`${result.termsAdded} written`, result.termIds);
|
|
15583
|
+
* for (const bad of result.errors ?? []) {
|
|
15584
|
+
* console.warn(`row ${bad.index} (${bad.feature}): ${bad.message}`);
|
|
15585
|
+
* }
|
|
15586
|
+
* ```
|
|
15260
15587
|
*/
|
|
15261
15588
|
async bulkCreateTerms(request, requestOptions) {
|
|
15262
15589
|
const wireRequest = BulkAddTermsRequestFromFrontToApi({
|
|
15590
|
+
...request,
|
|
15263
15591
|
terms: request.terms.map((t) => ({
|
|
15264
15592
|
...t,
|
|
15265
15593
|
features: convertFeatures(t.features)
|
|
@@ -15269,34 +15597,66 @@ var TermsClient = class {
|
|
|
15269
15597
|
return BulkAddTermsResponseFromApiToFront(response.data);
|
|
15270
15598
|
}
|
|
15271
15599
|
/**
|
|
15272
|
-
* List
|
|
15600
|
+
* List one page of the tenant's terms, and say how many there are.
|
|
15273
15601
|
*
|
|
15274
|
-
* @param query -
|
|
15275
|
-
*
|
|
15276
|
-
* @
|
|
15277
|
-
*
|
|
15278
|
-
*
|
|
15279
|
-
*
|
|
15280
|
-
*
|
|
15281
|
-
*
|
|
15282
|
-
*
|
|
15283
|
-
*
|
|
15284
|
-
*
|
|
15285
|
-
*
|
|
15286
|
-
*
|
|
15287
|
-
*
|
|
15288
|
-
*
|
|
15289
|
-
*
|
|
15290
|
-
*
|
|
15291
|
-
*
|
|
15602
|
+
* @param query - Paging, the sort filter, and `includeDerived`. Omit for
|
|
15603
|
+
* every term.
|
|
15604
|
+
* @param requestOptions - Per-call request options.
|
|
15605
|
+
* @returns The page in `terms`, its length in `count`, the size of the whole
|
|
15606
|
+
* answer in `total`, and an engine remark in `note`.
|
|
15607
|
+
* @throws {@link ApiError} If the request fails.
|
|
15608
|
+
*
|
|
15609
|
+
* @remarks
|
|
15610
|
+
* **Serialization format: Tagged (`ValueDto`).** Terms are enriched with sort
|
|
15611
|
+
* names, display names, and referenced-term summaries. Requires the
|
|
15612
|
+
* `X-Tenant-Id` header, which the client configuration sets.
|
|
15613
|
+
*
|
|
15614
|
+
* **Page off `total`, not `count`.** `count` is this page's length and
|
|
15615
|
+
* nothing else. Measured 2026-09-18 on a tenant holding 3 terms:
|
|
15616
|
+
* `GET /api/v1/terms?limit=1&offset=2` answered
|
|
15617
|
+
* `{"terms":[…one…],"count":1,"total":3}`. `total` is counted before the
|
|
15618
|
+
* window, so it is the number to compare an offset against.
|
|
15619
|
+
*
|
|
15620
|
+
* **When the page is empty, read `note` before you report "no results".**
|
|
15621
|
+
* The route neither chains nor persists conclusions, so for a sort whose
|
|
15622
|
+
* members exist only by derivation the honest answer in a freshly started
|
|
15623
|
+
* process is zero rows — and `total: 0` reads like an authoritative "no
|
|
15624
|
+
* members", which is false. Measured 2026-09-18, a tenant with `widget`,
|
|
15625
|
+
* subsort `premium_widget`, one `widget` fact and the rule
|
|
15626
|
+
* `widget(name: ?N) → premium_widget(name: ?N)`:
|
|
15627
|
+
* `GET /api/v1/terms?sort_name=premium_widget` answered
|
|
15628
|
+
* `{"terms":[],"count":0,"total":0,"note":"`premium_widget` is a rule
|
|
15629
|
+
* conclusion sort: its members are derived … An empty answer here does not
|
|
15630
|
+
* mean the sort has no members."}`. Without the rule, the same empty answer
|
|
15631
|
+
* carried no note.
|
|
15632
|
+
*
|
|
15633
|
+
* `sortName` filters on the sort's committed name, and on that sort EXACTLY
|
|
15634
|
+
* — a member of a subsort is not answered. For a plugin-contributed sort the
|
|
15635
|
+
* committed name is the namespaced form (`plugin:<plugin-name>:<local>`),
|
|
15636
|
+
* which {@link SortDto.name} carries and {@link SortDto.pluginLocalName}
|
|
15637
|
+
* maps back to the name its author wrote.
|
|
15638
|
+
*
|
|
15639
|
+
* @example
|
|
15640
|
+
* ```typescript
|
|
15641
|
+
* const first = await client.terms.listTerms({ sortName: 'person', limit: 50 });
|
|
15642
|
+
* if (first.terms.length === 0 && first.note) console.info(first.note);
|
|
15643
|
+
* for (let offset = 50; offset < (first.total ?? 0); offset += 50) {
|
|
15644
|
+
* const page = await client.terms.listTerms({ sortName: 'person', limit: 50, offset });
|
|
15645
|
+
* // …
|
|
15646
|
+
* }
|
|
15292
15647
|
*
|
|
15293
|
-
* //
|
|
15294
|
-
* const
|
|
15648
|
+
* // The asserted rows alone — the engine includes conclusions by default.
|
|
15649
|
+
* const asserted = await client.terms.listTerms({ includeDerived: false });
|
|
15295
15650
|
* ```
|
|
15296
15651
|
*/
|
|
15297
15652
|
async listTerms(query, requestOptions) {
|
|
15298
15653
|
const response = await this.api.listTerms(
|
|
15299
|
-
query ? {
|
|
15654
|
+
query ? {
|
|
15655
|
+
limit: query.limit,
|
|
15656
|
+
offset: query.offset,
|
|
15657
|
+
sort_name: query.sortName,
|
|
15658
|
+
include_derived: query.includeDerived
|
|
15659
|
+
} : void 0,
|
|
15300
15660
|
toRequestParams(requestOptions)
|
|
15301
15661
|
);
|
|
15302
15662
|
return TermListResponseFromApiToFront(response.data);
|
|
@@ -15325,12 +15685,25 @@ var TermsClient = class {
|
|
|
15325
15685
|
// ─── Friendly Aliases ─────────────────────────────────────────────
|
|
15326
15686
|
/**
|
|
15327
15687
|
* Create multiple records in a single request.
|
|
15328
|
-
* Alias for {@link bulkCreateTerms}.
|
|
15688
|
+
* Alias for {@link TermsClient.bulkCreateTerms}.
|
|
15689
|
+
*
|
|
15690
|
+
* @param request - The rows, plus `dryRun` and `partial`.
|
|
15691
|
+
* @param requestOptions - Per-call request options.
|
|
15692
|
+
* @returns What was written, exactly as {@link TermsClient.bulkCreateTerms}
|
|
15693
|
+
* returns it.
|
|
15694
|
+
* @throws {@link BulkRefusedError} 422 when nothing was written.
|
|
15695
|
+
*
|
|
15696
|
+
* @remarks
|
|
15697
|
+
* **Serialization format: Tagged (`ValueDto`).** Same call, friendlier name
|
|
15698
|
+
* — read {@link TermsClient.bulkCreateTerms} for the three outcomes and for
|
|
15699
|
+
* what a dry run does and does not report.
|
|
15329
15700
|
*
|
|
15330
|
-
* @
|
|
15331
|
-
*
|
|
15701
|
+
* @example
|
|
15702
|
+
* ```typescript
|
|
15703
|
+
* const result = await client.terms.createMany({ terms: rows, partial: true });
|
|
15704
|
+
* ```
|
|
15332
15705
|
*
|
|
15333
|
-
* @see bulkCreateTerms
|
|
15706
|
+
* @see {@link TermsClient.bulkCreateTerms}
|
|
15334
15707
|
*/
|
|
15335
15708
|
async createMany(request, requestOptions) {
|
|
15336
15709
|
return this.bulkCreateTerms(request, requestOptions);
|
|
@@ -15361,7 +15734,7 @@ var TermsClient = class {
|
|
|
15361
15734
|
return paginateByOffset(
|
|
15362
15735
|
async (window, perCall) => {
|
|
15363
15736
|
const page = await this.listTerms({ ...query, ...window }, perCall);
|
|
15364
|
-
return { items: page.terms };
|
|
15737
|
+
return page.total === void 0 ? { items: page.terms } : { items: page.terms, total: page.total };
|
|
15365
15738
|
},
|
|
15366
15739
|
options,
|
|
15367
15740
|
requestOptions
|
|
@@ -15369,10 +15742,6 @@ var TermsClient = class {
|
|
|
15369
15742
|
}
|
|
15370
15743
|
};
|
|
15371
15744
|
function convertFeatures(features) {
|
|
15372
|
-
const values = Object.values(features);
|
|
15373
|
-
if (values.length > 0 && values.every(isTaggedValueDto)) {
|
|
15374
|
-
return features;
|
|
15375
|
-
}
|
|
15376
15745
|
return toTaggedFeatures(features);
|
|
15377
15746
|
}
|
|
15378
15747
|
|
|
@@ -15423,6 +15792,14 @@ function TermInputDtoFromFrontToApi(model) {
|
|
|
15423
15792
|
if ("termId" in model) {
|
|
15424
15793
|
return { term_id: model.termId };
|
|
15425
15794
|
}
|
|
15795
|
+
if ("designator" in model) {
|
|
15796
|
+
return {
|
|
15797
|
+
designator: {
|
|
15798
|
+
sort_name: model.designator.sortName,
|
|
15799
|
+
features: model.designator.features
|
|
15800
|
+
}
|
|
15801
|
+
};
|
|
15802
|
+
}
|
|
15426
15803
|
if ("sortId" in model) {
|
|
15427
15804
|
const inline = {
|
|
15428
15805
|
sort_id: model.sortId,
|
|
@@ -15593,7 +15970,7 @@ function ForwardChainResponseFromApiToFront(dto) {
|
|
|
15593
15970
|
iterations: dto.iterations,
|
|
15594
15971
|
totalFacts: dto.total_facts,
|
|
15595
15972
|
materializationTimeMs: dto.materialization_time_ms,
|
|
15596
|
-
|
|
15973
|
+
keptCount: dto.kept_count,
|
|
15597
15974
|
provenanceTags: dto.provenance_tags?.map(ProvenanceTagDtoFromApiToFront)
|
|
15598
15975
|
};
|
|
15599
15976
|
}
|
|
@@ -16030,7 +16407,7 @@ function BackwardChainRequestFromFrontToApi(model) {
|
|
|
16030
16407
|
function ForwardChainRequestFromFrontToApi(model) {
|
|
16031
16408
|
return {
|
|
16032
16409
|
initial_facts: model.initialFacts?.map(TermInputDtoFromFrontToApi),
|
|
16033
|
-
|
|
16410
|
+
keep_derived: model.keepDerived,
|
|
16034
16411
|
enable_provenance_tags: model.enableProvenanceTags,
|
|
16035
16412
|
max_iterations: model.maxIterations,
|
|
16036
16413
|
max_facts: model.maxFacts,
|
|
@@ -16393,7 +16770,19 @@ var InferenceClient = class {
|
|
|
16393
16770
|
* Forward chaining starts from existing facts and applies rules to derive new facts,
|
|
16394
16771
|
* repeating until no more new facts can be derived (fixpoint) or limits are reached.
|
|
16395
16772
|
*
|
|
16396
|
-
*
|
|
16773
|
+
* `keepDerived: true` keeps the run's derivations RESIDENT so a later `MATCH`
|
|
16774
|
+
* reads them. A default run is rolled back, not merely unwritten — so this
|
|
16775
|
+
* route reports what it derived and leaves the store as it found it. Neither
|
|
16776
|
+
* setting is durable: to repair a tenant whose materialised set has drifted,
|
|
16777
|
+
* use OSFQL `CHAIN;` or {@link AdminClient.rebuildDerivedFacts}.
|
|
16778
|
+
*
|
|
16779
|
+
* `timeoutMs` is a server-side deadline in milliseconds, checked at every
|
|
16780
|
+
* fixpoint boundary and every rule application. Omitted means the engine's own
|
|
16781
|
+
* backstop (`OSFKB_FC_TIMEOUT_SECS`, 300 s by default); `0` opts out entirely.
|
|
16782
|
+
* The route answers `504` when the deadline passes, and the body says whether
|
|
16783
|
+
* a `keepDerived` run kept the partial derivation it had reached.
|
|
16784
|
+
*
|
|
16785
|
+
* @throws {ApiError} With status 504 when the derivation passed its deadline.
|
|
16397
16786
|
*/
|
|
16398
16787
|
async forwardChain(request, requestOptions) {
|
|
16399
16788
|
const wireRequest = {
|
|
@@ -16693,6 +17082,7 @@ function FindBySortRequestFromFrontToApi(model) {
|
|
|
16693
17082
|
sort_name: model.sortName ?? void 0,
|
|
16694
17083
|
filter: model.filter ?? void 0,
|
|
16695
17084
|
limit: model.limit ?? void 0,
|
|
17085
|
+
offset: model.offset ?? void 0,
|
|
16696
17086
|
include_derived: model.includeDerived
|
|
16697
17087
|
};
|
|
16698
17088
|
}
|
|
@@ -16833,11 +17223,97 @@ var QueryClient = class {
|
|
|
16833
17223
|
return response.data.results.map(TermDtoFromApiToFront);
|
|
16834
17224
|
}
|
|
16835
17225
|
/**
|
|
16836
|
-
*
|
|
17226
|
+
* Browse a sort and everything below it, one page at a time.
|
|
16837
17227
|
*
|
|
16838
|
-
* @param request -
|
|
16839
|
-
*
|
|
16840
|
-
* @
|
|
17228
|
+
* @param request - `sortId` (UUID) or `sortName`, an optional feature
|
|
17229
|
+
* `filter`, the `limit`/`offset` window, and `includeDerived`.
|
|
17230
|
+
* @param requestOptions - Per-call request options.
|
|
17231
|
+
* @returns The page in `terms`, its length in `count`, the size of the whole
|
|
17232
|
+
* answer in `total`, and an engine remark in `note`.
|
|
17233
|
+
* @throws {@link ApiError} 404 when no sort of that name is queryable for
|
|
17234
|
+
* the tenant.
|
|
17235
|
+
*
|
|
17236
|
+
* @remarks
|
|
17237
|
+
* **Serialization format: Tagged (`ValueDto`).** This is the polymorphic
|
|
17238
|
+
* browse: a query on a sort answers that sort AND every subsort of it, which
|
|
17239
|
+
* is what distinguishes it from {@link TermsClient.listTerms}'s exact
|
|
17240
|
+
* `sortName` filter.
|
|
17241
|
+
*
|
|
17242
|
+
* **Page off `total`.** `count` is this page's length. `total` is the number
|
|
17243
|
+
* of rows matched BEFORE `offset` and `limit`, counted rather than estimated.
|
|
17244
|
+
*
|
|
17245
|
+
* **A page is stable.** The answer is ordered by term id before the window
|
|
17246
|
+
* is applied, so page 2 neither repeats nor skips a row of page 1. Measured
|
|
17247
|
+
* 2026-09-18 against a 2-member sort:
|
|
17248
|
+
* `POST /api/v1/query/by-sort {"sort_name":"widget","include_derived":true}`
|
|
17249
|
+
* answered ids `829ef5dc-…` then `d4f7a4f8-…` with
|
|
17250
|
+
* `{"count":2,"total":2}`; the same request plus `{"limit":1,"offset":1}`
|
|
17251
|
+
* answered `d4f7a4f8-…` alone with `{"count":1,"total":2}` — the second row,
|
|
17252
|
+
* and the same total.
|
|
17253
|
+
*
|
|
17254
|
+
* **The route does not chain.** It answers the tenant's durable extension
|
|
17255
|
+
* UNIONED with the conclusions this process currently HOLDS. After a restart
|
|
17256
|
+
* the derived half is empty until something chains again. So when the page
|
|
17257
|
+
* is empty, read `note` rather than trusting `total: 0`: measured
|
|
17258
|
+
* 2026-09-18, a tenant with `widget`, subsort `premium_widget`, one `widget`
|
|
17259
|
+
* fact and the rule `widget(name: ?N) → premium_widget(name: ?N)`,
|
|
17260
|
+
* `{"sort_name":"premium_widget"}` answered
|
|
17261
|
+
* `{"terms":[],"count":0,"total":0,"note":"`premium_widget` is a rule
|
|
17262
|
+
* conclusion sort: its members are derived … Materialise them with OSFQL
|
|
17263
|
+
* CHAIN, POST /api/v1/inference/forward-chain, or POST
|
|
17264
|
+
* /api/v1/admin/derived-facts/rebuild/{tenant_id} … An empty answer here
|
|
17265
|
+
* does not mean the sort has no members."}`. Before the rule existed, the
|
|
17266
|
+
* same empty query carried no note.
|
|
17267
|
+
*
|
|
17268
|
+
* @example
|
|
17269
|
+
* ```typescript
|
|
17270
|
+
* const page = await client.query.findBySortPage({
|
|
17271
|
+
* sortName: 'sales_order',
|
|
17272
|
+
* includeDerived: true,
|
|
17273
|
+
* limit: 25,
|
|
17274
|
+
* offset: 0,
|
|
17275
|
+
* });
|
|
17276
|
+
* if (page.terms.length === 0 && page.note) console.info(page.note);
|
|
17277
|
+
* console.log(`${page.count} of ${page.total}`);
|
|
17278
|
+
* ```
|
|
17279
|
+
*
|
|
17280
|
+
* @see {@link QueryClient.findBySort} — the deprecated array-returning form.
|
|
17281
|
+
*/
|
|
17282
|
+
async findBySortPage(request, requestOptions) {
|
|
17283
|
+
const response = await this.api.findBySort(
|
|
17284
|
+
FindBySortRequestFromFrontToApi(request),
|
|
17285
|
+
toRequestParams(requestOptions)
|
|
17286
|
+
);
|
|
17287
|
+
return TermListResponseFromApiToFront(response.data);
|
|
17288
|
+
}
|
|
17289
|
+
/**
|
|
17290
|
+
* Browse a sort and everything below it, discarding the envelope.
|
|
17291
|
+
*
|
|
17292
|
+
* @deprecated Use {@link QueryClient.findBySortPage}, which returns the
|
|
17293
|
+
* engine's envelope. This method drops `count`, `total` and `note`, so a
|
|
17294
|
+
* caller cannot tell a full answer from a truncated one, cannot page, and
|
|
17295
|
+
* reads an empty array for a rule-conclusion sort with no way to see the
|
|
17296
|
+
* engine's explanation. It is kept so 1.27 callers keep compiling.
|
|
17297
|
+
*
|
|
17298
|
+
* @param request - `sortId` or `sortName`, an optional feature `filter`, the
|
|
17299
|
+
* `limit`/`offset` window, and `includeDerived`.
|
|
17300
|
+
* @param requestOptions - Per-call request options.
|
|
17301
|
+
* @returns The page's terms alone.
|
|
17302
|
+
* @throws {@link ApiError} 404 when no sort of that name is queryable for
|
|
17303
|
+
* the tenant.
|
|
17304
|
+
*
|
|
17305
|
+
* @remarks
|
|
17306
|
+
* **Serialization format: Tagged (`ValueDto`).** Identical request,
|
|
17307
|
+
* identical rows, identical ordering — see
|
|
17308
|
+
* {@link QueryClient.findBySortPage} for the measured ordering and paging
|
|
17309
|
+
* contract. The only difference is what is thrown away.
|
|
17310
|
+
*
|
|
17311
|
+
* @example
|
|
17312
|
+
* ```typescript
|
|
17313
|
+
* const terms = await client.query.findBySort({ sortName: 'sales_order' });
|
|
17314
|
+
* ```
|
|
17315
|
+
*
|
|
17316
|
+
* @see {@link QueryClient.findBySortPage}
|
|
16841
17317
|
*/
|
|
16842
17318
|
async findBySort(request, requestOptions) {
|
|
16843
17319
|
const response = await this.api.findBySort(FindBySortRequestFromFrontToApi(request), toRequestParams(requestOptions));
|
|
@@ -16945,10 +17421,6 @@ var QueryClient = class {
|
|
|
16945
17421
|
}
|
|
16946
17422
|
};
|
|
16947
17423
|
function convertPattern(pattern) {
|
|
16948
|
-
const values = Object.values(pattern.features);
|
|
16949
|
-
if (values.length > 0 && values.every(isTaggedValueDto)) {
|
|
16950
|
-
return pattern;
|
|
16951
|
-
}
|
|
16952
17424
|
return {
|
|
16953
17425
|
sortId: pattern.sortId,
|
|
16954
17426
|
features: toTaggedFeatures(pattern.features)
|
|
@@ -31399,6 +31871,11 @@ function OsfqlValueFromApiToFront(value) {
|
|
|
31399
31871
|
return typeof payload === "string" ? { type: "string", value: payload } : void 0;
|
|
31400
31872
|
case "boolean":
|
|
31401
31873
|
return typeof payload === "boolean" ? { type: "boolean", value: payload } : void 0;
|
|
31874
|
+
// The engine's own RFC 3339 rendering, passed through as the string it is.
|
|
31875
|
+
// Parsing it to a `Date` here would reformat a response, which this layer
|
|
31876
|
+
// never does — and would lose the offset the engine chose to send.
|
|
31877
|
+
case "datetime":
|
|
31878
|
+
return typeof payload === "string" ? { type: "datetime", value: payload } : void 0;
|
|
31402
31879
|
case "term_ref":
|
|
31403
31880
|
return typeof payload === "string" ? { type: "term_ref", value: payload } : void 0;
|
|
31404
31881
|
case "list": {
|
|
@@ -31532,6 +32009,7 @@ function OsfqlCatalogEntryFromApiToFront(dto) {
|
|
|
31532
32009
|
syntax: dto.syntax,
|
|
31533
32010
|
examples: dto.examples,
|
|
31534
32011
|
risk: dto.risk,
|
|
32012
|
+
mutates: dto.mutates,
|
|
31535
32013
|
execution: OsfqlCatalogExecutionFromApiToFront(dto.execution),
|
|
31536
32014
|
uiAffinity: {
|
|
31537
32015
|
display: dto.ui_affinity.display ?? void 0,
|
|
@@ -31550,6 +32028,74 @@ function OsfqlCatalogExecutionFromApiToFront(dto) {
|
|
|
31550
32028
|
if (dto === "PlanOnly") return { status: "planOnly" };
|
|
31551
32029
|
return { status: "partial", note: dto.Partial };
|
|
31552
32030
|
}
|
|
32031
|
+
function OsfqlPreviewSortCountFromApiToFront(dto) {
|
|
32032
|
+
return { sort: dto.sort, count: dto.count };
|
|
32033
|
+
}
|
|
32034
|
+
function OsfqlPreviewAffectedFromApiToFront(dto) {
|
|
32035
|
+
const affected = { count: dto.count };
|
|
32036
|
+
if (dto.exact !== void 0) {
|
|
32037
|
+
affected.exact = dto.exact;
|
|
32038
|
+
}
|
|
32039
|
+
if (dto.sample_rows !== void 0) {
|
|
32040
|
+
const rows = OsfqlBindingsFromApiToFront(dto.sample_rows);
|
|
32041
|
+
if (rows === void 0) {
|
|
32042
|
+
return void 0;
|
|
32043
|
+
}
|
|
32044
|
+
affected.sampleRows = rows;
|
|
32045
|
+
}
|
|
32046
|
+
if (dto.by_sort !== void 0) {
|
|
32047
|
+
affected.bySort = dto.by_sort.map(OsfqlPreviewSortCountFromApiToFront);
|
|
32048
|
+
}
|
|
32049
|
+
return affected;
|
|
32050
|
+
}
|
|
32051
|
+
function OsfqlAtomicRefusalFromApiToFront(dto) {
|
|
32052
|
+
return { code: dto.code, message: dto.message };
|
|
32053
|
+
}
|
|
32054
|
+
function OsfqlPreviewStatementFromApiToFront(dto) {
|
|
32055
|
+
const statement = {
|
|
32056
|
+
index: dto.index,
|
|
32057
|
+
id: dto.id,
|
|
32058
|
+
statement: dto.statement,
|
|
32059
|
+
risk: dto.risk,
|
|
32060
|
+
mutates: dto.mutates,
|
|
32061
|
+
sorts: dto.sorts,
|
|
32062
|
+
source: dto.source
|
|
32063
|
+
};
|
|
32064
|
+
if (dto.affected !== void 0) {
|
|
32065
|
+
const affected = OsfqlPreviewAffectedFromApiToFront(dto.affected);
|
|
32066
|
+
if (affected === void 0) {
|
|
32067
|
+
return void 0;
|
|
32068
|
+
}
|
|
32069
|
+
statement.affected = affected;
|
|
32070
|
+
}
|
|
32071
|
+
if (dto.nested !== void 0) {
|
|
32072
|
+
const nested = [];
|
|
32073
|
+
for (const child of dto.nested) {
|
|
32074
|
+
const parsed = OsfqlPreviewStatementFromApiToFront(child);
|
|
32075
|
+
if (parsed === void 0) {
|
|
32076
|
+
return void 0;
|
|
32077
|
+
}
|
|
32078
|
+
nested.push(parsed);
|
|
32079
|
+
}
|
|
32080
|
+
statement.nested = nested;
|
|
32081
|
+
}
|
|
32082
|
+
return statement;
|
|
32083
|
+
}
|
|
32084
|
+
function OsfqlPreviewResponseFromApiToFront(dto) {
|
|
32085
|
+
const statements = [];
|
|
32086
|
+
for (const entry of dto.statements) {
|
|
32087
|
+
const parsed = OsfqlPreviewStatementFromApiToFront(entry);
|
|
32088
|
+
if (parsed === void 0) {
|
|
32089
|
+
return void 0;
|
|
32090
|
+
}
|
|
32091
|
+
statements.push(parsed);
|
|
32092
|
+
}
|
|
32093
|
+
const response = { mutates: dto.mutates, statements };
|
|
32094
|
+
if (dto.atomic_refusal !== void 0) {
|
|
32095
|
+
response.atomicRefusal = dto.atomic_refusal ? OsfqlAtomicRefusalFromApiToFront(dto.atomic_refusal) : dto.atomic_refusal;
|
|
32096
|
+
}
|
|
32097
|
+
return response;
|
|
32098
|
+
}
|
|
31553
32099
|
|
|
31554
32100
|
// src/resources/osfql.ts
|
|
31555
32101
|
var OsfqlClient = class {
|
|
@@ -31625,6 +32171,107 @@ var OsfqlClient = class {
|
|
|
31625
32171
|
}
|
|
31626
32172
|
return parsed;
|
|
31627
32173
|
}
|
|
32174
|
+
/**
|
|
32175
|
+
* Preview an OSFQL program: what each statement would do, decided without
|
|
32176
|
+
* running anything.
|
|
32177
|
+
*
|
|
32178
|
+
* @param query - The OSFQL program text (one or more statements separated by `;`).
|
|
32179
|
+
* @param options - Optional request options. Only `atomic` changes the answer:
|
|
32180
|
+
* it decides whether `atomicRefusal` is reported. `reactive`, `maxRows` and
|
|
32181
|
+
* `timeoutMs` are accepted for wire parity with {@link execute} and are
|
|
32182
|
+
* ignored by the route.
|
|
32183
|
+
* @param requestOptions - Per-call transport options (timeout, signal, headers).
|
|
32184
|
+
* @returns The per-statement classification, the affected-row reports, and the
|
|
32185
|
+
* atomic refusal when there is one.
|
|
32186
|
+
* @throws {ApiError} If the program does not parse (HTTP 400, `OsfqlErrorResponse`),
|
|
32187
|
+
* or the request otherwise fails.
|
|
32188
|
+
* @throws {ReasoningLayerError} If a sample row carries a value that does not satisfy
|
|
32189
|
+
* the published {@link OsfqlValue} contract.
|
|
32190
|
+
*
|
|
32191
|
+
* @remarks
|
|
32192
|
+
* **Why call this instead of classifying the program client-side.** Three
|
|
32193
|
+
* answers only the engine's own parse can give:
|
|
32194
|
+
*
|
|
32195
|
+
* 1. **Per-statement `mutates`.** A client keeps no table of which
|
|
32196
|
+
* process-control statements write. Measured on the dev engine: `CHAIN` and
|
|
32197
|
+
* `RELEASE RESIDUATIONS` report `mutates: true`, while `MARK ?m1`, `CUT` and
|
|
32198
|
+
* `SPACE CREATE ?S` report `false` — all five are `risk: "process_control"`.
|
|
32199
|
+
* In the other direction, `MATCH customer(name: ?N) INSERT vip(name: ?N)`
|
|
32200
|
+
* is `risk: "read"` with `mutates: true`. Read `mutates`, never `risk`.
|
|
32201
|
+
* 2. **`affected.count` and `affected.sampleRows`.** Up to 10 rows, each
|
|
32202
|
+
* identifying itself with a `term_id` column and the declaring sort's
|
|
32203
|
+
* `@key` features, so a confirm dialog can say WHICH rows go, not just how
|
|
32204
|
+
* many. Counted by running a derived read-only `MATCH` against a clone of
|
|
32205
|
+
* the tenant. Verified: four destructive previews (`RETRACT`, `CLEAR FACTS`,
|
|
32206
|
+
* `DROP SORT`, `DEFINE`) left the tenant's fact count and its lattice
|
|
32207
|
+
* unchanged.
|
|
32208
|
+
* 3. **`atomicRefusal`.** The same `drop_sort_in_atomic_program` verdict
|
|
32209
|
+
* `POST /api/v1/osfql` would refuse the program with, answered before the
|
|
32210
|
+
* program runs.
|
|
32211
|
+
*
|
|
32212
|
+
* ⚠️ **The one thing a caller must NOT do: treat `affected.count` as the
|
|
32213
|
+
* number of rows the run will remove.** It is the reach of the statement's
|
|
32214
|
+
* PATTERN. For `MATCH customer(name: ?N, spend: ?S); IF ?S > 60 THEN RETRACT
|
|
32215
|
+
* customer(name: ?N);` over three customers of which one has `spend > 60`, the
|
|
32216
|
+
* nested `RETRACT` answers `count: 3` — the derived `MATCH customer(name: ?N)`
|
|
32217
|
+
* applies neither the `IF` condition nor the binding the earlier statement
|
|
32218
|
+
* gives `?N`. Present it as "up to N rows" for a nested statement or one that
|
|
32219
|
+
* reads a variable from an earlier statement.
|
|
32220
|
+
*
|
|
32221
|
+
* Two further measured facts. The top-level `mutates` DOES fold nested
|
|
32222
|
+
* statements now: the `IF` program above answers `true`, and so does an `IF`
|
|
32223
|
+
* whose only writing branch is the `ELSE` — the earlier defect where an `IF`
|
|
32224
|
+
* with a writing branch answered `false` is fixed. And `affected` being absent
|
|
32225
|
+
* is ambiguous: `RETRACT customer(tier: "bronze")` matching zero rows omits
|
|
32226
|
+
* the field entirely rather than answering `count: 0`, exactly as a read does.
|
|
32227
|
+
*
|
|
32228
|
+
* `statement.index` is a single counter over the whole program: an `IF` at
|
|
32229
|
+
* index 1 carries nested entries at index 2 and 3.
|
|
32230
|
+
*
|
|
32231
|
+
* Request serialization matches {@link execute} — `query` plus the optional
|
|
32232
|
+
* `atomic` / `reactive` / `max_rows` / `timeout_ms` keys. The program TEXT is
|
|
32233
|
+
* a string value, so the request bridge's snake_case pass does not touch it;
|
|
32234
|
+
* a `camelCase` feature name inside the query survives verbatim. Sample-row
|
|
32235
|
+
* KEYS are data and are not camelCased on the way back.
|
|
32236
|
+
*
|
|
32237
|
+
* @example
|
|
32238
|
+
* ```typescript
|
|
32239
|
+
* const preview = await client.osfql.preview(
|
|
32240
|
+
* 'MATCH customer(name: ?N); RETRACT customer(tier: "gold");'
|
|
32241
|
+
* );
|
|
32242
|
+
*
|
|
32243
|
+
* console.log(preview.mutates); // true
|
|
32244
|
+
* const writes = preview.statements.filter((s) => s.mutates);
|
|
32245
|
+
* console.log(writes[0].id); // "retract"
|
|
32246
|
+
* console.log(writes[0].risk); // "targeted_destructive"
|
|
32247
|
+
* console.log(writes[0].affected?.count); // 2
|
|
32248
|
+
* console.log(writes[0].affected?.sampleRows); // [{ term_id: …, tier: … }, …]
|
|
32249
|
+
*
|
|
32250
|
+
* // A DROP SORT cannot share an atomic program
|
|
32251
|
+
* const refused = await client.osfql.preview('DROP SORT widget; INSERT widget(label: "x");');
|
|
32252
|
+
* console.log(refused.atomicRefusal?.code); // "drop_sort_in_atomic_program"
|
|
32253
|
+
*
|
|
32254
|
+
* // Ask the same question without the atomic constraint
|
|
32255
|
+
* const loose = await client.osfql.preview(
|
|
32256
|
+
* 'DROP SORT widget; INSERT widget(label: "x");',
|
|
32257
|
+
* { atomic: false },
|
|
32258
|
+
* );
|
|
32259
|
+
* console.log(loose.atomicRefusal); // undefined
|
|
32260
|
+
* ```
|
|
32261
|
+
*/
|
|
32262
|
+
async preview(query, options, requestOptions) {
|
|
32263
|
+
const response = await this.api.previewOsfql(
|
|
32264
|
+
OsfqlRequestFromFrontToApi({ ...options, query }),
|
|
32265
|
+
toRequestParams(requestOptions)
|
|
32266
|
+
);
|
|
32267
|
+
const parsed = OsfqlPreviewResponseFromApiToFront(response.data);
|
|
32268
|
+
if (parsed === void 0) {
|
|
32269
|
+
throw new ReasoningLayerError(
|
|
32270
|
+
"osfql/preview returned an affected sample row that does not match the published OsfqlValue contract (expected tagged values with a lowercase `type` discriminator)"
|
|
32271
|
+
);
|
|
32272
|
+
}
|
|
32273
|
+
return parsed;
|
|
32274
|
+
}
|
|
31628
32275
|
/**
|
|
31629
32276
|
* Diagnose an OSFQL program for contradictions and inconsistencies.
|
|
31630
32277
|
*
|
|
@@ -35442,38 +36089,6 @@ var SolverClient = class {
|
|
|
35442
36089
|
}
|
|
35443
36090
|
};
|
|
35444
36091
|
|
|
35445
|
-
// src/normalizers/ontology-alignment.ts
|
|
35446
|
-
function AlignOntologyRequestFromFrontToApi(model) {
|
|
35447
|
-
return {
|
|
35448
|
-
domain_owl: model.domainOwl,
|
|
35449
|
-
targets: model.targets
|
|
35450
|
-
};
|
|
35451
|
-
}
|
|
35452
|
-
function AlignmentMatchDtoFromApiToFront(dto) {
|
|
35453
|
-
return {
|
|
35454
|
-
domainSort: dto.domain_sort,
|
|
35455
|
-
matchType: dto.match_type,
|
|
35456
|
-
targetCurie: dto.target_curie,
|
|
35457
|
-
targetLabel: dto.target_label
|
|
35458
|
-
};
|
|
35459
|
-
}
|
|
35460
|
-
function AlignmentConflictDtoFromApiToFront(dto) {
|
|
35461
|
-
return {
|
|
35462
|
-
domainSort: dto.domain_sort,
|
|
35463
|
-
targetA: dto.target_a,
|
|
35464
|
-
targetB: dto.target_b
|
|
35465
|
-
};
|
|
35466
|
-
}
|
|
35467
|
-
function AlignOntologyResponseFromApiToFront(dto) {
|
|
35468
|
-
return {
|
|
35469
|
-
conflicts: dto.conflicts.map(AlignmentConflictDtoFromApiToFront),
|
|
35470
|
-
domainSorts: dto.domain_sorts,
|
|
35471
|
-
mappingTtl: dto.mapping_ttl,
|
|
35472
|
-
matches: dto.matches.map(AlignmentMatchDtoFromApiToFront),
|
|
35473
|
-
targetSorts: dto.target_sorts
|
|
35474
|
-
};
|
|
35475
|
-
}
|
|
35476
|
-
|
|
35477
36092
|
// src/resources/ontology-alignment.ts
|
|
35478
36093
|
var OntologyAlignmentClient = class {
|
|
35479
36094
|
/** @internal */
|
|
@@ -38529,15 +39144,15 @@ function TemporalSeriesPointFromApiToFront(value) {
|
|
|
38529
39144
|
}
|
|
38530
39145
|
const bucketStart = asNumber(value["bucket_start"]);
|
|
38531
39146
|
const count = asNumber(value["count"]);
|
|
38532
|
-
const
|
|
38533
|
-
if (bucketStart === void 0 || count === void 0 ||
|
|
39147
|
+
const aggregate2 = asNumber(value["aggregate"]);
|
|
39148
|
+
if (bucketStart === void 0 || count === void 0 || aggregate2 === void 0) {
|
|
38534
39149
|
return void 0;
|
|
38535
39150
|
}
|
|
38536
39151
|
return {
|
|
38537
39152
|
bucketStart,
|
|
38538
39153
|
bucketEnd: asNumber(value["bucket_end"]),
|
|
38539
39154
|
count,
|
|
38540
|
-
aggregate,
|
|
39155
|
+
aggregate: aggregate2,
|
|
38541
39156
|
aggregateSecondary: asNumber(value["aggregate_secondary"])
|
|
38542
39157
|
};
|
|
38543
39158
|
}
|
|
@@ -42018,21 +42633,75 @@ var embeddings_exports = {};
|
|
|
42018
42633
|
// src/builders/value.ts
|
|
42019
42634
|
var Value = {
|
|
42020
42635
|
/**
|
|
42021
|
-
* Create a reference to another term by UUID
|
|
42636
|
+
* Create a reference to another stored term — by its UUID, or by the `@key`
|
|
42637
|
+
* values that name it.
|
|
42022
42638
|
*
|
|
42023
|
-
* @param
|
|
42024
|
-
*
|
|
42639
|
+
* @param target - The referenced term's UUID, or a {@link ReferenceDesignator}
|
|
42640
|
+
* naming it through the `@key` features of its sort.
|
|
42641
|
+
* @returns A tagged `ReferenceValue`: `{"type": "Reference", "value": "uuid"}`
|
|
42642
|
+
* for the id form, `{"type":"Reference","value":{"sort_name":…,"features":…}}`
|
|
42643
|
+
* for the designator form.
|
|
42644
|
+
* @throws {@link ValidationError} when the designator carries an empty
|
|
42645
|
+
* `sortName`, or names no feature — the engine refuses both, so the builder
|
|
42646
|
+
* refuses them before the round trip.
|
|
42025
42647
|
*
|
|
42026
42648
|
* @remarks
|
|
42027
|
-
* Serialization format: Tagged (ValueDto)
|
|
42649
|
+
* **Serialization format: Tagged (`ValueDto`).** Use with term CRUD, queries
|
|
42650
|
+
* and fuzzy operations. The homoiconic inference endpoints take the untagged
|
|
42651
|
+
* format and have no designator form.
|
|
42652
|
+
*
|
|
42653
|
+
* The two forms differ in what they can be used for:
|
|
42654
|
+
*
|
|
42655
|
+
* - The **UUID form** is what a read answers, and the only form that can
|
|
42656
|
+
* point at a term whose sort declares no `@key`.
|
|
42657
|
+
* - The **designator form** is WRITE-side only. It RESOLVES to a term that
|
|
42658
|
+
* already exists and never mints one: measured on 2026-09-18, writing
|
|
42659
|
+
* a `payment` whose `invoice` feature was
|
|
42660
|
+
* `Value.reference({ sortName: 'invoice', features: { number: { type: 'String', value: 'INV-1' } } })`
|
|
42661
|
+
* stored `{"type":"Reference","value":"4641cbcb-…"}` — the id of the single
|
|
42662
|
+
* existing invoice — and the invoice extent still held exactly one term. A
|
|
42663
|
+
* designator matching nothing is refused `422 no term of sort 'invoice'
|
|
42664
|
+
* carries @key 'number' = "INV-NOPE"; a reference designator names an
|
|
42665
|
+
* existing term, it never creates one`. The same refusal arrives from
|
|
42666
|
+
* `POST /api/v1/terms/bulk` as a `BulkRefusedError` naming the entry index.
|
|
42667
|
+
*
|
|
42668
|
+
* The designator's `features` keys are OSF feature names — user data, not
|
|
42669
|
+
* schema — and survive the request bridge verbatim, so a feature declared
|
|
42670
|
+
* `invoiceNumber` must be spelled `invoiceNumber` here. Sending the
|
|
42671
|
+
* snake_cased spelling is refused: `422 feature 'invoice_number' of sort
|
|
42672
|
+
* 'invoice' is not a @key feature; a designator addresses a term only through
|
|
42673
|
+
* its @key`.
|
|
42028
42674
|
*
|
|
42029
42675
|
* @example
|
|
42030
42676
|
* ```typescript
|
|
42677
|
+
* // By id:
|
|
42031
42678
|
* Value.reference("550e8400-e29b-41d4-a716-446655440000")
|
|
42679
|
+
*
|
|
42680
|
+
* // By @key, when the caller has the business key and not the UUID:
|
|
42681
|
+
* Value.reference({
|
|
42682
|
+
* sortName: 'invoice',
|
|
42683
|
+
* features: { number: { type: 'String', value: 'INV-1' } },
|
|
42684
|
+
* })
|
|
42032
42685
|
* ```
|
|
42033
42686
|
*/
|
|
42034
|
-
reference(
|
|
42035
|
-
|
|
42687
|
+
reference(target) {
|
|
42688
|
+
if (typeof target === "string") {
|
|
42689
|
+
return { type: "Reference", value: target };
|
|
42690
|
+
}
|
|
42691
|
+
if (target.sortName.length === 0) {
|
|
42692
|
+
throw new ValidationError(
|
|
42693
|
+
"Value.reference() expects a designator with a non-empty sortName"
|
|
42694
|
+
);
|
|
42695
|
+
}
|
|
42696
|
+
if (Object.keys(target.features).length === 0) {
|
|
42697
|
+
throw new ValidationError(
|
|
42698
|
+
`Value.reference() expects a designator naming at least one @key feature of sort "${target.sortName}"; the engine refuses an empty designator`
|
|
42699
|
+
);
|
|
42700
|
+
}
|
|
42701
|
+
return {
|
|
42702
|
+
type: "Reference",
|
|
42703
|
+
value: { sortName: target.sortName, features: target.features }
|
|
42704
|
+
};
|
|
42036
42705
|
},
|
|
42037
42706
|
/**
|
|
42038
42707
|
* Create a reference to a sort by UUID.
|
|
@@ -42771,6 +43440,7 @@ var FuzzyShape = {
|
|
|
42771
43440
|
};
|
|
42772
43441
|
|
|
42773
43442
|
// src/builders/psi.ts
|
|
43443
|
+
var NEGATION_SORT = "negation";
|
|
42774
43444
|
function psi(sortOrName, features) {
|
|
42775
43445
|
if (typeof sortOrName === "string") {
|
|
42776
43446
|
if (!features) {
|
|
@@ -42799,6 +43469,38 @@ function bind(name, term) {
|
|
|
42799
43469
|
}
|
|
42800
43470
|
return { ...term, binding: name };
|
|
42801
43471
|
}
|
|
43472
|
+
function not(clause) {
|
|
43473
|
+
if ("sortName" in clause && clause.sortName === NEGATION_SORT) {
|
|
43474
|
+
throw new ValidationError(
|
|
43475
|
+
`not() cannot negate a negation: the chainers read the "${NEGATION_SORT}" carrier one clause deep, so a nested negation is accepted and derives nothing`
|
|
43476
|
+
);
|
|
43477
|
+
}
|
|
43478
|
+
return {
|
|
43479
|
+
__psiTerm: true,
|
|
43480
|
+
sortName: NEGATION_SORT,
|
|
43481
|
+
features: { clause }
|
|
43482
|
+
};
|
|
43483
|
+
}
|
|
43484
|
+
function aggregate(spec) {
|
|
43485
|
+
if (spec.groupBy.length === 0) {
|
|
43486
|
+
throw new ValidationError(
|
|
43487
|
+
"aggregate() expects at least one groupBy feature; an aggregator with no group key has no group to fold into"
|
|
43488
|
+
);
|
|
43489
|
+
}
|
|
43490
|
+
const blank = spec.groupBy.find((name) => name.trim().length === 0);
|
|
43491
|
+
if (blank !== void 0) {
|
|
43492
|
+
throw new ValidationError("aggregate() expects every groupBy entry to be a feature name");
|
|
43493
|
+
}
|
|
43494
|
+
if (spec.target.trim().length === 0) {
|
|
43495
|
+
throw new ValidationError("aggregate() expects a target feature name");
|
|
43496
|
+
}
|
|
43497
|
+
if (spec.groupBy.includes(spec.target)) {
|
|
43498
|
+
throw new ValidationError(
|
|
43499
|
+
`aggregate() cannot aggregate "${spec.target}" and also group by it`
|
|
43500
|
+
);
|
|
43501
|
+
}
|
|
43502
|
+
return { groupBy: [...spec.groupBy], op: spec.op, target: spec.target };
|
|
43503
|
+
}
|
|
42802
43504
|
function constrained(name, constraint) {
|
|
42803
43505
|
return { __constrainedVar: true, name, constraint };
|
|
42804
43506
|
}
|
|
@@ -43483,6 +44185,7 @@ exports.AuthenticationError = AuthenticationError;
|
|
|
43483
44185
|
exports.Authz = authz_exports;
|
|
43484
44186
|
exports.BadRequestError = BadRequestError;
|
|
43485
44187
|
exports.Batch = batch_exports;
|
|
44188
|
+
exports.BulkRefusedError = BulkRefusedError;
|
|
43486
44189
|
exports.CDL = cdl_exports;
|
|
43487
44190
|
exports.Causal = causal_exports;
|
|
43488
44191
|
exports.Chase = chase_exports;
|
|
@@ -43594,6 +44297,7 @@ exports.Visualization = visualization_exports;
|
|
|
43594
44297
|
exports.WebSocketClient = WebSocketClient;
|
|
43595
44298
|
exports.WebSocketConnection = WebSocketConnection;
|
|
43596
44299
|
exports.WebhookActions = webhook_actions_exports;
|
|
44300
|
+
exports.aggregate = aggregate;
|
|
43597
44301
|
exports.allen = allen;
|
|
43598
44302
|
exports.bind = bind;
|
|
43599
44303
|
exports.collect = collect;
|
|
@@ -43604,6 +44308,7 @@ exports.isConstrainedPlainVar = isConstrainedPlainVar;
|
|
|
43604
44308
|
exports.isPsiTermInput = isPsiTermInput;
|
|
43605
44309
|
exports.isTaggedValueDto = isTaggedValueDto;
|
|
43606
44310
|
exports.isUuid = isUuid;
|
|
44311
|
+
exports.not = not;
|
|
43607
44312
|
exports.paginateByOffset = paginateByOffset;
|
|
43608
44313
|
exports.paginateByPage = paginateByPage;
|
|
43609
44314
|
exports.psi = psi;
|