Interface: FeatureDescriptorDto
@kortexya/reasoninglayer / FeatureDescriptorDto
Interface: FeatureDescriptorDto
Section titled “Interface: FeatureDescriptorDto”Defined in: src/types/sorts.ts:88
Descriptor for a named feature within a sort definition.
Defines the feature name, expected sort (type), whether the feature is required, optional constraints, and a type hint for LLM extraction.
Remarks
Section titled “Remarks”This is used in arrays (not Records). The name field identifies the feature.
Properties
Section titled “Properties”annotations?
Section titled “annotations?”
optionalannotations?:Record<string,string>
Defined in: src/types/sorts.ts:267
Custom OWL annotations on this feature — isIdentifier, unit,
enumValues, and whatever else the tenant’s model needs.
Remarks
Section titled “Remarks”The keys are DATA, so they survive the request body’s snake_case
transform untouched. Measured: a create carrying
annotations: { isIdentifier: 'true', unit: 'none' } reads back
"annotations": {"isIdentifier": "true", "unit": "none"} — the
camelCase key intact.
key: true is stored as the unique annotation, so this map echoes it.
cardinalityOrigin?
Section titled “cardinalityOrigin?”
optionalcardinalityOrigin?:CardinalityOriginDto|null
Defined in: src/types/sorts.ts:253
Where the bounds above came from, and therefore whether a write may be refused for breaking them.
Remarks
Section titled “Remarks”A bound sent without this field is declared: measured, a create
carrying minCount: 2, maxCount: 3 and no origin reads back
"cardinality_origin": "declared", and the write path then enforces it.
inferred records a bound read out of an extracted axiom. It is
carried, exported and diffed — and it refuses nothing: a term that
breaks an inferred bound is written. The derivation of required is off
too: an inferred minCount: 2 reads back "required": false.
Sending it back unchanged is the point of the field. The ordinary way to amend a sort is read → edit → create, and without the origin beside the bound that round trip would promote every bound the extraction pipeline inferred into a declared one, and start refusing the ingestion that wrote it.
An ordinary caller may send inferred only as that unchanged echo:
SortsClient.createSort and SortsClient.bulkCreateSorts
accept it when the tenant already stores an inferred bound for the
feature, on this sort or an ancestor, with the same minCount and
maxCount, and the sort’s own record declares no bound for that
feature. Anything else labelled inferred — a new bound, a changed one,
or a declared bound relabelled — is a declaration, and is refused: 400
from createSort, an unsatisfiable_declaration entry from
bulkCreateSorts, with a message that starts
sort 'S': feature 'F':. An ancestor’s inferred bound does not let a
sort relabel its own declared bound. A new sort therefore cannot carry
an inferred bound; to move an extracted sort, use
SortsClient.reparentSort rather than a delete and re-create.
A word outside the two is refused with a 422 naming both.
constraint?
Section titled “constraint?”
optionalconstraint?:ConstraintDto|null
Defined in: src/types/sorts.ts:116
Optional constraint on the feature’s value.
constraintMessage?
Section titled “constraintMessage?”
optionalconstraintMessage?:string|null
Defined in: src/types/sorts.ts:318
What a writer whose value constraint refuses is told
(Pick small, medium or large).
Remarks
Section titled “Remarks”A refused write reports it as
NamedConstraintViolationError.constraintMessage; a SHACL export
emits sh:message.
constraintName?
Section titled “constraintName?”
optionalconstraintName?:string|null
Defined in: src/types/sorts.ts:308
A stable name for constraint (pizza_size_is_known).
Remarks
Section titled “Remarks”A write the constraint refuses reports it as
NamedConstraintViolationError.constraintName; a SHACL export emits
it as sh:name.
description?
Section titled “description?”
optionaldescription?:string|null
Defined in: src/types/sorts.ts:283
Human-readable prose about the feature, used for semantic embeddings and documentation.
Remarks
Section titled “Remarks”A read answers it whenever the stored declaration carries one, so the ordinary read → edit → create amendment sends it back unchanged.
On an amendment — SortsClient.createSort for a sort that already
exists, and SortsClient.bulkCreateSorts — an absent or blank
description KEEPS the stored one, and a non-blank value replaces it. No
value clears it through this field. A description-only amendment is
applied: the declaration counts as changed. To clear it, use
SortsClient.patchSortFeature with description: null.
expectedSort?
Section titled “expectedSort?”
optionalexpectedSort?:string|null
Defined in: src/types/sorts.ts:92
Expected sort (type) for this feature’s value.
expectedSortName?
Section titled “expectedSortName?”
optionalexpectedSortName?:string|null
Defined in: src/types/sorts.ts:152
The same target, named instead of identified — how an object relation is declared when the target sort’s id does not exist yet.
Remarks
Section titled “Remarks”Resolved only by POST /api/v1/sorts/bulk
(SortsClient.bulkCreateSorts), which mints its ids server-side:
a relation between two sorts of the SAME batch has no id its caller
could write into FeatureDescriptorDto.expectedSort. The name is
matched against the batch’s own name → id map and the tenant’s existing
sorts.
Measured — one bulk request declaring Invoice.issued_to with
expectedSortName: 'Client' alongside a Client definition reads back
"expected_sort": "7e764fb0-…", the id of the Client the same batch
created. A name that resolves to nothing is reported in the response’s
errors and the feature is left unbound; it is never silently dropped.
expectedSort wins when both are given.
expectedTypeHint?
Section titled “expectedTypeHint?”
optionalexpectedTypeHint?:string|null
Defined in: src/types/sorts.ts:94
Primitive type hint for LLM extraction (e.g., “DateTime”, “Integer”, “String”, “Real”).
index?
Section titled “index?”
optionalindex?:IndexDirectiveDto|null
Defined in: src/types/sorts.ts:299
How the feature takes part in the retrieval indexes.
Remarks
Section titled “Remarks”Absent means the declaration is silent about indexing, which is NOT the
same as not_indexed. A read answers the directive whenever the stored
declaration states one.
On an amendment — SortsClient.createSort for a sort that already
exists, and SortsClient.bulkCreateSorts — an absent index KEEPS
the stored directive, and a value replaces it. not_indexed is a
directive, not a clear: no value returns a stated directive to silence
through this field. To return it to silence, use
SortsClient.patchSortFeature with index: null.
optionalkey?:boolean
Defined in: src/types/sorts.ts:131
Declare this feature @key / @unique — an extensional functional
dependency over the sort’s subsort-closed extent.
Remarks
Section titled “Remarks”A write carrying a key value an existing term already holds is that
term. An identical duplicate coreferences into it (createTerm answers
200 with the existing term); a conflicting one is refused with
@key clash … (⊥).
This is what gives an application’s model identity. Without it a second
createTerm for the same customer makes a second customer, and every
later join is ambiguous.
maxCount?
Section titled “maxCount?”
optionalmaxCount?:number|null
Defined in: src/types/sorts.ts:216
The greatest number of values this feature may hold.
Remarks
Section titled “Remarks”maxCount binds under BOTH world modes, unlike
FeatureDescriptorDto.minCount. Measured on a sort declaring
lines with minCount: 2, maxCount: 3, writing four values is refused
identically on an open and on a closed sort:
422 Constraint violation: Feature 'lines' holds 4 values, above its declared maximum of 3. Over-supply is a contradiction whatever the
world assumption; under-supply is only missing information.
maxCount: 0 is accepted and reads back { min_count: 0, max_count: 0 }
— a feature declared to hold no value.
Omitting it is the unbounded maximum; the OSFQL spelling of the pair is
@card(min) / @card(min, max), with * for unbounded.
⚠️ Enforcement is gated on
FeatureDescriptorDto.cardinalityOrigin — an inferred bound
refuses nothing. See that field.
minCount?
Section titled “minCount?”
optionalminCount?:number|null
Defined in: src/types/sorts.ts:193
The least number of values this feature must hold — the bound required
cannot express.
Remarks
Section titled “Remarks”minCount binds only under a closed world. Measured against the
engine on a sort declaring lines with minCount: 2, maxCount: 3:
| values written | worldMode: 'open' |
worldMode: 'closed' |
|---|---|---|
| 0 | 201 created | 422 required feature 'lines' is missing |
| 1 | 201 created | 422 feature 'lines' holds 1 values, below its declared minimum of 2 |
| 2 | 201 created | 201 created |
On an open sort a short or absent feature RESIDUATES — it is
unconstrained, not wrong — so a schema that means its minimum must also
say CreateSortRequest.worldMode closed.
FeatureDescriptorDto.maxCount needs no such help.
A non-zero minCount IS FeatureDescriptorDto.required. The
engine keeps the two consistent in one direction only: measured,
{ minCount: 2, required: false } reads back "required": true, and
{ minCount: 2 } with no required also reads back "required": true.
The reverse does not hold — { required: true } alone reads back with
NO minCount at all. And { required: true, minCount: 0 } is refused
with a 400: “a feature that may hold no value at all is NOT
required”. OSFQL @card(0, n) required is the same contradiction, and
is a parse error on execute and preview.
A minCount above FeatureDescriptorDto.maxCount is refused with
a 400 — “declares min_count 5 above max_count 2, so no term could
ever satisfy it” — rather than stored unsatisfiable.
A read answers 0 here whenever only maxCount was declared: measured,
{ maxCount: 3 } reads back { min_count: 0, max_count: 3 }.
⚠️ Enforcement is gated on
FeatureDescriptorDto.cardinalityOrigin — an inferred bound
refuses nothing. See that field.
name:
string
Defined in: src/types/sorts.ts:90
Feature name.
required
Section titled “required”required:
boolean
Defined in: src/types/sorts.ts:114
Whether this feature must be present on instances of the sort.
Remarks
Section titled “Remarks”Enforced only when the sort’s CreateSortRequest.worldMode is
closed. On an open sort a missing feature residuates — it is
unconstrained, not absent — so required: true there constrains nothing.
This is the same statement as minCount >= 1 at a coarser resolution,
and the engine derives it: measured, a descriptor sent with
{ minCount: 2, required: false } reads back "required": true. The
implication runs one way only — required: true alone stores no
minCount — and the contradictory pair
{ required: true, minCount: 0 } is refused with a 400. The OSFQL
spelling of that pair, @card(0, n) required or required @card(0), is
refused too: OsfqlClient.execute and OsfqlClient.preview throw an
OsfqlError with errorType: 'parse' and its line and column, rather
than storing a minimum of 1. See FeatureDescriptorDto.minCount.