Changelog
All notable changes to this project are documented in this file. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[5.1.0] - 2026-10-15
Section titled “[5.1.0] - 2026-10-15”Built against engine main 95caf5925. No breaking change.
- OSFQL editor tokens (reasoninglayer-ts-sdk#60):
client.osfql.tokens(query)callsPOST /api/v1/osfql/tokensand answersOsfqlTokensResponse, the editor tokens of the source in UTF-16 coordinates, each with anOsfqlTokenKind. - Resumable RDF uploads (reasoninglayer-ts-sdk#60), every route under
/api/v1/ingest/rdf/uploads:client.ingestion.createRdfUpload,listRdfUploads,getRdfUpload,uploadRdfChunk,completeRdfUploadandpauseRdfUpload, with the typesRdfUpload,CreateRdfUploadRequest,RdfUploadChunkReceiptandRdfUploadPauseReceipt.uploadRdfChunk(id, offset, chunk)sends the chunk as rawapplication/octet-streambytes. A resent chunk with the same bytes answers the same receipt; different bytes at an offset already stored are refused. PatternDiscoveryRequest.continuationon the generated transport (discoverPatternProposals,exportPatternContext): send the continuation of the previous complete-mode page, with the same scope and filters, to receive the next page. The engine answers409when the scope or filters changed.
Changed
Section titled “Changed”- The transport layer and its doc comments follow the engine spec at
95caf5925: attribute-exploration completion registers its appropriateness sorts before the terminal commit and answers the committed receipt again on retry; a confirm/refute answer commits only on the session version it was checked against; a pipeline stage’smetricscount what itsbasisnames.
[5.0.0] - 2026-10-15
Section titled “[5.0.0] - 2026-10-15”Built against engine main feafe936f (reasoninglayer#584 E0-E11, engine !400-!411, and the engine changes merged since cfd780622). Two parts: the #584 S1 resync (truthful pipeline stages, completeness fields) and the governed controlled vocabulary resource, client.vocabulary (#584 S2).
Migrating to 5.0.0
Section titled “Migrating to 5.0.0”Each item names one BREAKING change. The items marked (run time) change behaviour that the compiler does not report: grep for them.
Pipeline stages are assessed, not counted
-
Stop reading
PipelineStageStatus.complete. The engine removed it (engine #584 C0.2): it was derived from counts, so a flat sort list with two levels read as a “complete taxonomy”. There is no replacement flag and no alias. Readstate('not_assessed' | 'stale' | 'accepted'), what it rests on inbasis(discriminated bykind:'legacy_sort_metrics', or'vocabulary_release'withreleaseId,releaseNumber,assessmentId,contextId,assessedAt), and thereasonin words. Only thevocabularystage can leavenot_assessed, from an active governed vocabulary release whose bound assessment is complete and passed; the other five stages are alwaysnot_assessed.metricsare unchanged and decide nothing.// 4.xconst done = status.stages.filter((s) => s.complete).length;// 5.0.0const accepted = status.stages.filter((s) => s.state === 'accepted');for (const s of status.stages) console.log(s.stage, s.state, s.reason); -
OntologyPipelineStageis a closed union of the six stages; the openstringarm is gone, because the engine now declares the stage as an enum. -
Handle a
503fromontology.pipelineStatus()(run time): aVocabularyRefusedErrorwithcode: 'vocabulary_not_committed'means the vocabulary store could not be read; nothing is served partially. It is not aServiceUnavailableError: the body is a vocabulary refusal, and every such body is read as one.
Results that were not complete
- Handle
OptimizationResultstatus: 'undetermined'(run time).optimize.solve()andfromKnowledgeBase()reportedinfeasiblefor every empty backward-chain answer, including one from a bounded search.infeasiblenow requires the engine to report a complete derivation; an empty answer from a bounded or pending derivation isundetermined(UndeterminedResult). An exhaustiveswitchonstatusneeds the new arm. - Read the completeness before reading an empty answer (run time):
BackwardChainResponse.derivationComplete/openWorldSearch,FuzzyProveResponse.derivationComplete/openWorldSearch,NafProveResponse.derivationComplete,BayesianPredictResponse.derivationComplete,AuthzCheckResponse.derivationComplete,NlQueryResponse.derivationComplete,ForwardChainResponse.completion,OsfqlResponse.chainCompletions/proveCompletions,EvaluateCompetencyQuestionsResponse.complete/incomplete,CheckDocumentResponse.complete/passes,SortDiscoveryResponse.incompleteReason/inputComplete/search, the explorationsearchreports,SolveConstraintResponse.unsatisfiable,CoherenceReport.unfinishedDerivationRunsandTemporalSeriesResponse.selectionComplete. The SDK dropped all of them before.
Unions that gained an arm (an exhaustive switch needs it)
ProofKind'truncated',CoherenceVerdict'undetermined'(withcheckedandchainIterations; never a certificate),CompetencyQuestionVerdict'undetermined',CronJobStatus'executing',CronJobRunStatus'interrupted',AudioJobStatus'interrupted',RememberOutcome'unresolved_contradiction',FindingKind'scope_incomplete',OutputPredicate{ TracePredicate },DocumentSource/AudioSource{ stored: { id } }, and the code unionsTermWriteErrorCode/SortCreateErrorCode'governed_sort_write_refused'.
Request and response types that tightened
- Set
DiscoveryConfigDto.changeMode('absolute' | 'relative') whenever you send a discoveryconfig: the engine’s published request contract requires it, and the SDK does not pick a change quantity for you. Send noconfigto use the engine defaults. This requirement comes from a defect in the engine’s spec, not from the engine’s behaviour: the engine accepts a config withoutchange_modeand appliesabsolute(reasoninglayer#593). A later release makeschangeModeoptional again once the spec is fixed. - Read
DiscoverEffectsResponse.discoveredHorizonsas camelCase (supportWeight,sampleCount);HorizonDto.confidenceandEffectPredictionDto.confidenceare optional and alwaysnullfor this estimator. - Add the new required fields to every object you build by hand (test fixtures):
PipelineStageStatus.state/basis/reason;OntologyQualityReport.competencyAssessment;EvaluateCompetencyQuestionsResponse.complete/incomplete;OsfqlResponse.chainCompletions/proveCompletions;ValidatedUnifyResponse.witnessesFailed;AuthzCheckResponse.decisionTime;CheckDocumentResponse.complete/passes;SortDiscoveryResponse.incompleteReason/inputComplete/inputStopReason/search/registrations; the exploration responses’search;IntegratedCycleOutcomeDto.goalsDeferred/simulationResults;CycleOutcomeSummaryDto.goalsDeferred;AgentBeliefDto.shared;CronJobRun.origin;RunCronJobNowResponse.runId;InstallSummary.bundleId;GlbResponse.maximalLowerBounds;LubResponse.minimalUpperBounds;SolveConstraintResponse.unsatisfiable/violatedConstraintIds/suspendedConstraintIds;CoherenceLink.scope;CoherenceReport.unfinishedDerivationRuns;ConversationMessageResponse.frameAmbiguities/frameRetries/gateAbstained;TemporalSeriesResponse.selector/selectionComplete;DetectCommunitiesResponse.graphObservation/levels/config/hierarchyStopReasonandgraphObservationon the centrality, cohesion, path and link-prediction responses; theDiscoverEffectsResponseandPredictFromDiscoveryResponseaccounting fields; andVerdictKindProvenSafe.binding. Code that only reads them is unaffected. - Read
SortDiscoveryResponse.fuzzyLevelsasFuzzyConceptLevel[]; it wasunknown[]. - Catch
ResponseShapeErrorwhere a schema-erased field breaks the engine’s documented shape (run time): the discovery accounting, the community graph observation, the echoed term-set selector, the certificate binding and the extraction-workerexecution/graphare now read field by field, and a body that does not match fails loudly instead of reaching you half-read.
The governed controlled vocabulary: client.vocabulary (engine #584 E9/E10, every route under /api/v1/vocabulary)
- A vocabulary change is a candidate of immutable revisions of knowledge-organization records (schemes, concepts, definitions, label uses, memberships, evidence, source groups, authority references, identity decisions, aboutness). It is assessed, submitted, reviewed and approved by a steward who did not author it, released as an immutable release, and activated: activation swaps the tenant’s active-release pointer (compare-and-set) and materializes the records as Psi-terms of the
com.kortexya.knowledge-organizationplugin in one transaction. Approval and release change neither the active release nor the terms; nothing creates a sort, a parent edge or a rule. - 31 methods, one per route:
status,active,history,listSchemes,listConcepts,getConcept,conceptHistory,search,createCandidate,listCandidates,getCandidate,saveRevision,getRevision,diffRevision,rebase,assess,getAssessment,submit,review,approve,reject,withdraw,listDecisions,release,listReleases,getRelease,listReleaseRecords,activate,getRequestOutcome,getContext,migrationPreview. Every 2xx is decoded throughdecodeResponse. - Typed discriminated unions for the change set:
VocabularyRecordInput(bykind),VocabularyRecordRef(byby:recordorclient_ref),VocabularyRecordOperation(byop:upsertorremove) and the storedVocabularyRecordView. Reads take aVocabularyViewSelector(active,release+releaseId,candidate+revisionId); a candidate view saysdraftNotActive: true. Closed literal unions for every code, state and severity (VocabularyRefusalCode,VocabularyFindingCode,VocabularyFindingSeverity,VocabularyCandidateState,VocabularyReleaseState,VocabularyRecordErrorCode, …). The types are also under theVocabularynamespace. - Idempotency. Every mutation takes a required
idempotencyKeyin its options (VocabularyMutationOptions); the SDK never generates one, so bind it to a variable before the call and pass the same variable on every retry. An identical repeat answers the committed outcome withreplayed: true. Save and rebase also take a requiredifMatch(the head revision id or its ETag;VocabularyRevisionMutationOptions). NewidempotencyParams(key, options?, ifMatch?)inrequest-params.ts, besideifMatchParams. - Acting user. Every mutation is made by the client’s
authenticatedUser(X-Authenticated-User: user:<uuid>). An author and an approver are two clients. - Refusals (engine: every vocabulary
4xxexcept404and412answersVocabularyRefusalResponse):VocabularyRefusedErrorwith the closedcode,current,findings,conflicts,authority,pluginand the whole typedrefusal(recordErrors,findingIds,missingRelations,storedRequestHash,rebaseUrl,recovery). SubclassesIdempotencyKeyRequiredError(428),IdempotencyKeyReusedError(422,storedRequestHash) andMutationOutcomeUnconfirmedError(503,recovery: never conclude that nothing was written; readgetRequestOutcomeor repeat with the same key).getRequestOutcomedecodes the stored body as the operation’s own typed result (VocabularyCommittedResult, discriminated byoperation); a stored body that is not that response throwsResponseShapeError.invalid_requestarrives with400,413,415or422; the activation500materialized_vocabulary_driftis aVocabularyRefusedErrortoo. A404for an unknown record stays aNotFoundError, and a staleIf-Matchstays aPreconditionFailedError(currentEtagnames the head).vocabularyRefusalOfBody(body)reads a refusal from any error body. VocabularyFindingDispositionInput.rationaleis required (engine E9).- Contract suite
tests/contract/vocabulary.contract.test.tsruns the M1 first slice (base_create,v11_definition_change) on a fresh, seeded tenant and erases it;pnpm run capture-fixtures --vocabularyrecords verbatim engine bodies for the unit tests.
The S1 resync
ontology.theoryFingerprint()(GET /api/v1/ontology/theory/fingerprint, engine #584 C0.2): digests of the tenant’s own sort declarations and rules (definition,declarationSet,ruleSet,excluded). Plugin and engine contributions are excluded and only counted, so a plugin install changes neither digest.OntologyQualityReport.competencyAssessment(state: 'none_stated' | 'not_run',acceptedQuestions): whycompetencyPassRateis absent. The report runs no question.EvaluateCompetencyQuestionsResponse.completeandincomplete, and theundeterminedverdict.- Typed
WorkerProposalonExtractEntitiesResponse.workerProposal(engine #584 W1.0), withexecutionandgraphasReadonly<Record<string, unknown>>, andExtractEntitiesRequest.worker({ sourceId, sourceRevision }) to request one. The proposal is advisory:nativeAdmissionis alwaysfalse. - Completeness:
OpenWorldSearchCompletion/OpenWorldLimit,ForwardChainCompletion/FactIdentityIssue,OsfqlChainCompletion/OsfqlProveCompletion/OsfqlRowVerdict,WitnessFailure,AggregationEvidenceonProofDto.aggregation,ExplorationSearchReport,FcaSearchSummary,SortRegistration. OsfqlRequest.inference: recompute deductions in a private read snapshot without publishing a change.AuthzCheckRequest.decisionTimeandAuthzCheckResponse.decisionTime.analysis.advanceExploration(sessionId, bounds?): resume a suspended exploration search. Start, confirm and refute takemaxWork,maxCandidatesandsearchTimeoutMs; sort discovery takesmaxWork,maxVisitedConceptsandmaxSourceScan.ingestion.resumeAudioJob(jobId)for aninterruptedaudio job;originalFileIdon parsed-document, audio and image-extraction metadata;{ stored: { id } }document and audio sources that reuse a retained original.- Discovery:
targetUnit,candidateHorizons,minHorizonScore,asOf, and the full window, observation, outcome and support accounting (ChangeAccounting,EffectAccounting,DiscoveryWindow,DiscoveryObservation,ForwardChange,PredictionSupport). - Communities: hierarchical detection (
hierarchical,maxLevels,seed,graphView),CommunityDto.parentCommunityId,CommunityLevel,HierarchyStopReason,ResolvedCommunityDetectionConfig, and theReferenceGraphObservationevery graph metric now reports. - Conversation frame mode:
ConversationMessageRequest.queryMode/queryModel/contextTermIds, andframeMode,frameAmbiguities,frameRecordId,frameRetries,gateAbstained,queryFrameon the response. GlbResponse.maximalLowerBoundsandLubResponse.minimalUpperBounds;SolveConstraintResponserefutation fields;CoherenceLink.scope(ClauseComparisonScope);CronJobRunorigin and interruption instants;RunCronJobNowResponse.runId;RememberResult.contradictionCheckIncomplete;AgentBeliefDto.sharedandAddBeliefRequest.shared;InstallSummary.bundleId;EnrichedHealthResponse.sovereign;artifactFilesDeletedon the tenant clear and factory reset;EvaluateFunctionRequest.expectedResult;CertificateBinding/ClaimKindon aProvenSafeverdict.Marketplace.MarketplaceErrorCode:plugin_not_persisted,plugin_has_governed_data,governed_data_unverified,plugin_sort_identity_changed.pnpm run check-normalizer-dropsruns intest:ci.
Changed
Section titled “Changed”- Doc comments follow the engine:
@governedsorts refuse every client write and delete door withgoverned_sort_write_refused(term routes409, OSFQL422refusal code); a system-wide marketplace operation is durable-first and answers503 plugin_not_persistedwhen the registry did not store it; disable and uninstall refuse a plugin whose@governedsorts hold records. - Cron docs follow the engine’s current scheduler (run time):
timezoneaccepts onlyUTC(any other zone is a400), andjitterMsandmaxRetriesare reserved (a nonzero value is a400; retries default to0).cron.runNowrefuses a job whose agent is missing, paused or stopped, whose source is not registered, or that has no program registration, with409. - A cognitive cycle (basic or integrated) on a tenant that holds a native agent belief is refused with
422agent_belief_operation_unsupportedand changes nothing (run time). NewCognitive.AgentBeliefErrorCodenames the four belief refusal codes. - The reason a bulk action-review decision gives for an unknown review is
Not found: Review <id> not found; another tenant’s sort as a parent answersSort not found: SortId(<id>), byte-identical to an unknown id. - The cognitive-agent contract suite runs on its own tenant and deletes it afterwards; the g6a cron tests create a real agent for
runNow.
Not wrapped yet
Section titled “Not wrapped yet”The regenerated transport layer carries these engine routes; no SDK method wraps them in this change: query models, the authoring context and application plan, the inbound-webhook hook programs, chunked file upload, the heartbeat simulator, the OSFQL capabilities / token / query-generation routes, the frame execute and frame schema routes, the pattern-proposal routes, completeJoin / completeMeet, model discovery and validation, the temporal profile, UI validation, the egress and sovereignty reads, the source-file download, and the approver-role revocation.
[4.2.0] - 2026-10-03
Section titled “[4.2.0] - 2026-10-03”Built against engine main cfd780622 (engine !321: #475-#480, !322: #481-#487).
conversation.getConversation(id)(engine #478): reads one conversation’s summary, with itskindandtitle, without reading the whole listing.ConversationMessageResponse.notices(engine #477): what the engine did to the question before it answered, typed bykind(pruned_features,value_grounding,retrieval_rescue). New typesConversationNotice,SkippedFeatureandValueSubstitution. The notice sentences no longer appear insuggestions, which holds follow-up suggestions only.repairedandoriginalOsfqlare now also set by a prune, a grounding, the retrieval rescue and the no-code builder.ProofDto.conclusionandSolutionDto.conclusion(engine #476): the conclusion as a structured term,ProofConclusionDto(sortId,sortName,termId, taggedfeatures). Read the sort and features here, not fromgoalDisplay.UIAssemblyStatsDto.dataLoadErrorandUIGenerateResponse.dataLoadErrors(engine #475): a failed data load as a typedDataLoadError(code,message,query).SessionDocument.errorCode(engine #479): the typed reason a session document failed,SessionDocumentFailureCode.DocumentStatsDto.entitiesRefused(engine #480): the entities the declaration verdict refused. It is on the session stats, the document stats and the markdown receipt.BulkSortErrorKindgainsamendment_refused(engine #482): an amendment the lattice refused (a contradicted subsort refinement, a subtree over the cap, a parent edge closing a cycle). It answerssortCreated: true.DocumentResult.summarydoc follows engine #487: the summary is deterministic engine text, not LLM prose.
Changed
Section titled “Changed”ConversationMessageResponse.noticesandDocumentStatsDto.entitiesRefusedare required, as on the wire. A fixture you build for either type must carry them.UIAssemblyStatsDto.osfqlExecutedis absent on a failed data load. It no longer starts withFAILED:; readdataLoadErrorinstead.- Doc comments follow the engine behaviour of !321 and !322:
ui.executeActionupdate_formedits the term in place (#481),affectedPreviewcounts a refused statement as 0 (#485), and the rulederivationsRemovedandderivationsWeakenedcount resident conclusions (#484).
inference.addRule,validateRule,bulkAddRulesandreplaceRulethrow aValidationErrornaming the field (antecedents, orrules[k].antecedents) for a request without anantecedentsarray, not a rawTypeError(SDK #58). An empty array stays the engine’s decision.
[4.1.0] - 2026-10-02
Section titled “[4.1.0] - 2026-10-02”Built against engine main 308ed2a25 (engine !320: #473, #474).
AddRuleResponse.etagandReplaceRuleResponse.etag(engine #473):inference.addRuleandreplaceRulefold theETagresponse header into the result.replaceRule’s tag is the REPLACEMENT’s. The field is absent when the engine sends no header, which it does only when the read that tags the response fails after the write committed.RuleEntryDto.etagoninference.getRules(engine #473): the tag of each withdrawable rule; absent on a rule no route withdraws.
Changed
Section titled “Changed”GeneratedScenario.sortsis optional (engine #474). No scenario key is required now: every collection defaults to empty, so a vocabulary import sendssortsalone.GeneratedScenario: the engine no longer reads the snake_case spellingswebhook_actions,temporal_constraints,guard_constraints,pipeline_stagesandsensor_points(engine #474). It ignores them like any unlisted key, without an error. SendwebhookActions,temporalConstraints,guardConstraints,pipelineStagesandsensorPoints.materializeScenariosends the scenario verbatim, so the SDK does not rewrite a key in either direction;ontology.generate()returns the camelCase spellings.MaterializeScenarioRequestFromFrontToApipasses the scenario through without a type assertion.
inference.deleteRuleandreplaceRuledocs: read a rule’sifMatchtag fromaddRule,replaceRuleor thegetRuleslisting. They said to read it withterms.getTerm(ruleId), which answers404for a rule since engine #456.TermResponse.etagandterms.getTermdocs are corrected the same way. This closes the 4.0.0 known engine gap reasoninglayer#473.- Contract test g5a reads the rule’s tag from the
addRuleresult and the listing, not fromterms.getTerm.
[4.0.0] - 2026-10-02
Section titled “[4.0.0] - 2026-10-02”Built against engine main 5cef4fa34 (v0.1.151-58). 3.1.0 and 3.2.0 shipped without changelog entries; the entries below cover every change since 3.2.0.
Migrating to 4.0.0
Section titled “Migrating to 4.0.0”Each item names one BREAKING change listed under Changed — BREAKING or Removed — BREAKING below, and what to change in your code. The items marked run time change behaviour that the compiler does not report: grep for them.
Removed calls and fields
- Drop
platform.listBranchesandplatform.forkBranch, and the imports ofConversationBranch,BranchListResponseandForkBranchRequest. There is no replacement: the engine removed both routes. - Stop reading
DeleteGoalResponse.referencesDeletedandSortDeletePreviewResponse.orphanedForeignChildren: the engine no longer sends them.orphanedChildrenlists every child a sort delete would orphan. - Delete
UIActionRequest.originalValuesfrom every UI action, and always send the row’s term UUID astermId. - Delete
IngestionConfigDto.batchSizeandmaxSchemaTokensfrom every ingest call. The engine refuses either key with422. - Delete
tenantIdandownerIdfromAddPendingReviewRequest,tenantIdfromReExtractRequest, andreviewedByfrom the bulk review requests. Send each review candidate as{ termId, similarity }. - Stop reading
ErrorResponse.details.
Changed signatures
authz.deleteTuple(tupleId, options?, requestOptions?): move request options from the second argument to the third.conversation.listConversations(query?, requestOptions?): move request options from the first argument to the second.analysis.confirmImplication(sessionId, { questionId }, requestOptions?): pass the pending question’sExplorationQuestion.questionId, and move request options to the third argument. SetRefuteRequest.questionIdtoo.reviews.getSummary()takes no tenant id: callgetSummary()orgetSummary(requestOptions).
Request bodies the engine now checks
- Give every rule at least one antecedent (
addRule,validateRule,replaceRule,bulkAddRules):antecedentsis required. Write a rule with no antecedents as a fact, withterms.createTerm. - A
not(bind('?T', …))needs a positive antecedent that binds?T(run time): drop thebindto keep the old “no such term exists” meaning. A nestedbind()in a positive feature value now joins on that identity; drop it there too if you did not mean the join. - Write each scenario rule as a
Scenarios.ScenarioRule({ head, antecedents?, certainty? }, each term{ sort, features?, negated?, binding? }) inGeneratedScenario.rulesandUpdateScenarioRequest.addRules. - Set
RecordTurnRequest.conversationKindto'guided_ontology'or'external_agent'only. - Use
FlowRelOp('le' | 'ge' | 'eq' | 'lt' | 'gt' | 'ne') in thecomparearm of a feasibilityBoolExpr, not the oldRelOpvalues. - Use
SymbolVisibilityDto('public' | 'private' | 'protected') for a module symbol’svisibility. - Narrow the strings you assign to
Argument.label(ArgumentLabel),Attack.kind(ArgumentAttackKind),ReExtractRequest.extractionStrategy(ExtractionStrategy),CertificateDetail.verdict/method,UIResultShape,TurnFeedback.rating/ConversationAnswerFeedbackResponse.rating(AnswerRating) and the residuationkindfilter (ResiduationKindFilter). - Annotate a rename body with
PatchSortFeatureRenamewhere you extended or indexedPatchSortFeatureRequest, and narrow with'name' in reqbefore you read.name.
Response shapes
- Re-read these responses through their new types, because the old types named keys the engine never sent:
OsfSearchResponse.entities/relations(OsfSearchEntityDto,OsfSearchRelationDto),AteEstimateResponse.certificate(DoublyRobustCertificateDto),FormalJudgeResponse.violations(FormalJudgeViolationDto),ExecutionSessionStatsResponse, and the flatPendingReviewDto(now the ingestion one). - Read
UIDescribeResponse.dataas aUIDataPayload: switchtotal_count/term_idstototalCount/termIds. - Parse
CreateConstraintSessionResponse.createdAtas an ISO 8601 string, not a number. - Handle
nullinDocumentAnalysisReport.overallComplianceandDocumentResult.complianceScore:nullmeans no applicable rule was evaluated, or (for a document) that it was read only in part. - Filter conversations on
c.kind === 'assistant', not on an absentkind:ConversationSummaryDto.kindis required. - Add the new arms to every exhaustive
switch: the twelve new fuzzyEffectDtoarms,ReviewReason're_extraction',OsfqlErrorType'lock_timeout'andWaitingConditionType'variable_binding'. Remove the'suspended'and'ready'residuation states, andResiduationStats.ready/suspended. - Move imports of
ViolationDtofrom theOversightnamespace toGeneration, and ofSortSuggestionDtofromReviewstoCognitive. ReadIngestion.PendingReviewDtowhere you readReviews.PendingReviewDto. The flat exports are unchanged. - Name
IngestionSessionDetailResponsewhere you named the return type ofingestion.getSessionin a position that needs the exact type. - Add the new required fields to every object you build by hand (test fixtures):
sourceFilesDeleted,inboxItemsDeletedanddmPairingStateDeleted(tenant clear, delete and factory reset);entitiesRefused(IngestionStatsDto);turnId(ConversationMessageResponse);complete(SparqlTranslation);entityTermIdandentityOrigin(PendingReviewEntity);documentId(ReExtractResponse);inventionTermId(TrizNoveltyScore); and all sevenConformanceViolationDtofields. Code that only reads them is unaffected. - Catch
ResponseShapeErrorandReasoningLayerErrorwhere a malformed response used to pass through (run time): an unknown fuzzy effect arm, an off-scale rating, and an unreadable TRIZ novelty score now throw.
Errors and status codes (run time)
- Branch on
OsfqlErrorwitherrorType === 'lock_timeout', not onServiceUnavailableError/InternalServerError, for a503fromosfql.execute. - Branch on
errorType === 'refused'/400, not'execution'/422, for an OSFQLDROP SORTorALTER SORT … PARENTSbeside another statement, or a reparent onto an unknown parent. - Move review-decision error branches:
BadRequestError/400for a decided review becomesConstraintViolationError/409, andForbiddenError/403for another tenant’s review becomesNotFoundError/404. - Read a
sendMessageStreamrefusal of an unknown or foreign conversation from itserrorevent (status404 or 409), not from a thrown error.
Changed — BREAKING
Section titled “Changed — BREAKING”-
query.osfSearchentities and relations take the route’s real shape (engine !309).OsfSearchResponse.entitiesisOsfSearchEntityDto[](termId,sortId,matchDegree,communityId,features,name?,matchReason?) andrelationsisOsfSearchRelationDto[](sourceId,targetId,relationType,confidence,discoveryMethod). The query-areaMatchedEntityDtoandDiscoveredRelationDtoare removed: they named keys the route never sends, so every field readundefined. The flatMatchedEntityDtoexport is now the conversation one. -
AteEstimateResponse.certificateis aDoublyRobustCertificateDto(productBiasFactorization,consistentUnderCorrectPropensity,consistentUnderCorrectOutcome,allCertified) oncausal.estimateAteand its ATT/DML/TMLE/clustered siblings. It was typed as the oversightCertificateDto, so every field readundefined. -
FormalJudgeResponse.violationsisFormalJudgeViolationDto[](layer,description,factId,constraintType?) onoversight.judge,refineand sessionfinalize. The oldseverityandrelatedFactIdnever existed on this route.ViolationDtomoves from theOversightnamespace toGeneration, the area that sends it; the flat export is unchanged. -
CreateConstraintSessionResponse.createdAtis an ISO 8601 string, not a number: the engine serializes a UTC date-time. -
ExecutionSessionStatsResponseis{ memorySessionCount, curriedFunctionCount, dbAvailable }. The old{ documents, sessionId, total }was the ingestion shape and never matched the route. -
Module-symbol
visibilityisSymbolVisibilityDto('public' | 'private' | 'protected'). The namespaceVisibilityDtoit used admittedinternalandrestricted, which the module routes refuse. -
Solver
RelOpis replaced byFlowRelOp('le' | 'ge' | 'eq' | 'lt' | 'gt' | 'ne') in thecomparearm of a feasibilityBoolExpr. The old spelled-out values ('less_than_or_equal', …) belong to the constraint solver and were refused bybeginFeasibilitySession. -
Fuzzy
EffectDtohas all fifteen membership-function arms of the engine’sFuzzyEffectDto(sigmoid,bell,sigmoid_difference,gaussian_product,sigmoid_product,cosine,spike,cauchy,s_shape,z_shape,pi_shape,piecewise_linear, besidesgaussian,triangularandtrapezoidal). Code that switches exhaustively onPredictEffectResponse.effectmust add the new arms. An arm the SDK does not know now throwsResponseShapeError(with the status, method, path and body); before, any arm but the first three was reported as a fabricated{ gaussian: { mean: 0, stdDev: 0 } }. -
UIDescribeResponse.datais aUIDataPayload(columns,rows,totalCount,termIds?), mapped likeui.generate’s payload. It was an untypedobjectpassed through raw, so callers read snake_casetotal_count/term_ids. Switch those reads tototalCount/termIds. -
UIResultShapeis the closed union of the engine’sReturnsenum ('Bindings' | 'TermIds' | 'Scalar' | 'List' | 'Path' | 'Boolean' | 'None'); the openstringwidening is gone. -
ConformanceViolationDtofields (subject,object,feature,subjectSort,objectSort,kind,suggestedFixes) are required: the engine publishes the component with all seven required. Only code that constructs one is affected. -
The cognitive cycle’s
SortSuggestionDtomoves from theReviewsnamespace toCognitive, the area that returns it. The flat export is unchanged. -
CertificateDetail.verdictis typedVerdictKindTag('ProvenSafe' | 'Falsified' | 'EmpiricallySafe' | 'Residuated') andmethodisProofMethod; both werestring. -
ErrorResponselosesdetails: no engine error body carries it. -
authz.deleteTuple(tupleId, options?, requestOptions?): the second argument is now{ ifMatch?: string }; per-call request options move to the third. A caller that passed request options second must move them. -
The flat
PendingReviewDtois the ingestion one (candidates: CandidateMatchDto[]of{ termId, similarity },entityId,reason,sort): the shape the ingest responses carry inpendingReview. TheReviews.PendingReviewDtois removed: it carriedReviewCandidateMatchDtocandidates, matched no wire shape, and no normalizer produced it. ReadIngestion.PendingReviewDto, or the flat name. -
UIActionRequest.originalValuesis removed (engine #172). The engine refuses anoriginal_valueskey with422, andupdate_formno longer reads it: it identifies the row bytermIdalone, which must be a term UUID (else400). Drop the field, and always send the row’s term id. -
reviews.getSummary()takes no tenant id (engine !304, #87 #366). The route is nowGET /api/v1/reviews/summary, with the tenant taken from the principal; the old/reviews/summary/{tenant_id}is gone. CallgetSummary()(orgetSummary(requestOptions)).ReviewSummaryaddstotalApproved,totalRejected,totalCorrected,totalMerged,avgPendingConfidence(0when nothing is pending) andoldestPending?(RFC 3339), andbyReasonis nowPartial<Record<ReviewReason, number>>; a key outsideReviewReasonthrowsValidationError. The confidence bands are now computed, no longer a constant0. -
Review request bodies lose the fields the engine now refuses with
422(engine !304).AddPendingReviewRequestdropstenantIdandownerId, addsreasonDetail?, and takescandidates: ReviewCandidateInput[]({ termId, similarity }; the old fullReviewCandidateMatchDtois refused).ReExtractRequestdropstenantId, andextractionStrategyis anExtractionStrategy(it was astring);documentIdmust be a registry document id.BulkApproveRequest,BulkRejectRequestandBulkMergeRequestdropreviewedBy: the reviewer is the principal. Migration: delete those fields from every call, and send each candidate as{ termId, similarity }. -
ReviewReasonadds're_extraction'(engine !304), the reason of an entity a re-extraction found. An exhaustiveswitchoverReviewReasonneeds the new arm.Ingestion.PendingReviewDto.reason(thependingReviewentries of the ingest responses) is now aReviewReason, not astring, and addsreasonDetail?. -
PendingReviewEntityadds the requiredentityTermIdandentityOrigin(ReviewEntityOrigin:'proposed' | 'provisional' | 'existing'), andreasonDetail?;ReExtractResponseadds the requireddocumentId, the registry id of the new document version. Only code that builds these objects by hand is affected. -
An OSFQL
DROP SORTorALTER SORT … PARENTSbeside another statement isrefused(HTTP 400), notexecution(HTTP 422) (engine !305, #365).osfql.executethrowsOsfqlErrorwitherrorType: 'refused'and the newatomicRefusal(code:drop_sort_not_alone/alter_sort_not_alone,message), the same codeosfql.previewreports. AnALTER SORT … PARENTSthat names an unknown or foreign parent is also arefused400 (Cannot reparent sort 'S': parent sort X does not exist); an unknown subject sort stays anexecution422. The type surface does not break, but code that branches onerrorType === 'execution'or on status422for these programs must move to'refused'/400. -
DeleteGoalResponse.referencesDeletedis removed (engine !306, #288). The engine dropped thedelete_referencesquery parameter and the always-0references_deletedcount.inference.deleteGoal(goalId)deletes the goal with every term it owns (its clause, query, constraint and variable terms) and never a tenant fact the goal only references. The response is{ deleted, goalId }. -
ClearTenantResponse,DeleteTenantResponseandFactoryResetResponseadd the requiredsourceFilesDeleted,inboxItemsDeletedanddmPairingStateDeleted(engine !308, #381, #358): the uploaded source files, unified-inbox items and DM pairing records (policies, allowlisted senders and pairing requests, summed) the wipe erased. The mapper dropped all three.rowsDeletedTotaldoes not count the inbox or pairing records: the engine erases them through their stores before the table sweep. Only code that builds one of these objects by hand must add the fields. -
Argument.labelisArgumentLabel('in' | 'out' | 'undecided') andAttack.kindisArgumentAttackKind('value_conflict' | 'polarity_conflict' | 'deontic_conflict' | 'rule_derived_conflict'), engine !306 (#76). Both werestring; the wire values did not change. Only code that assigns an arbitrary string to them stops compiling. -
ConversationSummaryDto.kindis a requiredConversationKind('assistant' | 'guided_ontology' | 'generative_ui' | 'external_agent', engine !310). Every conversation carries its kind: an assistant chat carries'assistant', so a test for an absent kind (!c.kind) now matches nothing. Filter onc.kind === 'assistant', or list one kind withlistConversations({ kind }). The engine no longer lists a conversation without a valid kind. -
conversation.listConversations(query?, requestOptions?)(engine !310): the first argument is now aListConversationsQuery({ kind?: ConversationKind }), sent as?kind=<kind>; per-call request options move to the second. A caller that passed request options first must move them. An unknown kind is aBadRequestError. -
RecordTurnRequest.conversationKindis aRecordableConversationKind('guided_ontology' | 'external_agent'), not a freestring(engine !310). The engine refusesassistantandgenerative_uiwith400, an unknown kind with422, and a kind other than the stored one with409(ConstraintViolationError). An absent kind on create now meansexternal_agent. -
IngestionConfigDto.batchSizeandmaxSchemaTokensare removed (engine !311, #352). The engine refuses either key with422on/ingest/markdown,/ingest/markdown/batch,/ingest/document,/ingest/document/batchand/ingest/audio. Neither had an effect. The LTN and source-ingestbatchSizefields are different DTOs and stay. -
ingestion.getSessionandIngestionSessionHandle.getSessionreturnIngestionSessionDetailResponse(engine !311, #400): the session fields anddocuments: SessionDocument[].IngestionFailedError.sessionis the detail response too. The result still satisfiesIngestionSessionResponse; only code that names the return type in a position that needs the exact type is affected. -
analysis.confirmImplication(sessionId, request, requestOptions?)(engine !314, #410): the second argument is now a requiredConfirmRequest({ questionId }), sent as{ "question_id": … }; per-call request options move to the third.RefuteRequest.questionIdis required too. Pass the pending question’sExplorationQuestion.questionId(new on every question the start, confirm, refute and status calls return). An id that is not the pending question — a replayed answer, a stale id, an id after completion — answers409with codeexploration_question_not_pending(ConstraintViolationError; read the code witherrorBodyCode) and nothing is applied. An older client gets415(confirm) or422(refute). -
AddRuleRequest.antecedentsis required (engine !312, #375, #390), and so isantecedentsin the request ofinference.addRule,validateRule,replaceRuleand everybulkAddRulesrow. The engine refuses a rule with no antecedents (422 rule rejected: a rule needs at least one antecedent; a term with no antecedents is a fact — write it through POST /api/v1/terms): write a fact withterms.createTerm.validateRulewith an empty list answersok: falsewith arefusedissue. InbulkAddRulesan empty row is refused per row inrejected[]and not counted; a row without the key fails the whole batch with422. Everyrejected[].reasonis now the single route’s422text, prefixedrule rejected:(the oldrule head term did not resolve: …andantecedent N could not be resolvedtexts are gone). -
The residuation listing drops the states it never held (engine !314, #93).
ResiduationStateDtois'pending' | 'completed' | 'failed'andResiduationStateFilteris'pending' | 'completed' | 'failed' | 'all'('suspended'and'ready'are gone;'failed'is a new filter).ResiduationStatslosesreadyandsuspended, and its counts describe the returned rows.ResiduationKindloses'all'; the request’skindis the newResiduationKindFilter(ResiduationKind | 'all').WaitingConditionTypeadds'variable_binding': an exhaustiveswitchneeds the arm. -
TrizNoveltyScore.inventionTermIdis required, and a malformed novelty score throws (engine !313, #383). Entry i ofNlQueryResponse.noveltyScoresscores the i-thtriz_inventive_proposalofresults, so the mapper no longer skips an entry it cannot read (that shifted every later score onto the wrong proposal): it throwsReasoningLayerErrornaming the index. -
TurnFeedback.ratingandConversationAnswerFeedbackResponse.ratingare typedAnswerRating(-1 | 0 | 1), notnumber(sdk#55). The mapper checks the value: a rating off the scale is reported as aResponseShapeError, not passed through. Only code that builds one of these objects with a plainnumberstops compiling. -
A
503lock_timeoutfromosfql.executeis now anOsfqlError, not aServiceUnavailableError/InternalServerError(engine !306, #288).OsfqlErrorextendsApiErroronly, so an existinginstanceof ServiceUnavailableErrororinstanceof InternalServerErrorbranch for/osfqlstops matching, and no compiler reports it: branch onerrorType === 'lock_timeout'.OsfqlErrorTypeadds'lock_timeout': other writers kept the tenant’s knowledge base locked until the request deadline; nothing ran, so retry it as is or send a largertimeoutMs. The engine can answer it before a read-onlyMATCHtoo;osfql.previewnever does. The transport’sretryOn503still applies first. An exhaustiveswitchoverOsfqlErrorTypeneeds the new arm. -
PatchSortFeatureRequestis now a union type alias (PatchSortFeatureRename | PatchSortFeatureAmendment, engine !305, #389). Rename calls compile unchanged, but code that reads or extends the type breaks:interface X extends PatchSortFeatureRequestno longer compiles, andreq.name/PatchSortFeatureRequest['name']isstring | undefined. Extend or annotate withPatchSortFeatureRenamefor a rename body, and narrow with'name' in reqbefore you read.name. -
Review decisions answer
409and404where they answered400and403(engine !304). The type surface does not break, butapproveEntity,correctEntity,mergeEntityandrejectEntitynow throwConstraintViolationError(409) for a review that is no longer pending (it wasBadRequestError,400), andNotFoundError(404) for another tenant’s review (it wasForbiddenError,403). Code that branches onBadRequestError/400for a decided review, or onForbiddenError/403for another tenant’s review, must move toConstraintViolationError/409andNotFoundError/404. -
DocumentAnalysisReport.overallComplianceandDocumentResult.complianceScorearenumber | nullondocuments.analyze(engine !315, #411).nullmeans that no applicable rule was evaluated, so nothing is shown about compliance. A truncated document has anullscore, and its rules do not count towardoverallCompliance: nopasscan stand on a partial read. The SDK reported an absent score as0, which reads as “fails every rule”. -
IngestionStatsDto.entitiesRefusedis required (RefusedEntityDto[]:termId,sortName,message,feature?,chunkIndex?; engine !315, #433). Ingestion judges each extracted entity by the same declaration verdict asPOST /api/v1/termsand lists every entity it refused, so it was not written. An older engine that omits the list reads as[]. -
ConversationMessageResponse.turnIdis required (string, engine !317, #466): the assistant turn the answer was stored as, the idsubmitAnswerFeedbackrates. Every answer is stored, the certify-or-abstain refusal and the could-not-process reply included, so every answer carries it.sendMessage, the streameddoneevent anddecodeConversationMessageall carry it; a payload withoutturn_iddoes not decode (decodeConversationMessagereturnsnull, and the stream throwsNetworkError). -
SparqlTranslation.completeis required (boolean, engine !317, #464). It says whetherosfqlanswers the query when run verbatim (true, one statement) or holds plan fragments that only the engine’s bridge joins (false). The mapper dropped it before. Check it before you run any ofosfql. -
bind()on a nested term keeps its binder (engine #438). In 3.2.0 the nested-term conversion dropped it by design, and the same call now sends a different body with a different meaning. On the clause ofnot(),not(bind('?T', psi('meat_topping')))changes fromNOT EXISTS meat_topping()(“no such term exists”) toNOT EXISTS ?T: meat_topping()(“?Tis not one”); when no positive antecedent binds?T, the engine now refuses the rule with422. A nestedbind()in a positive feature value now adds an identity join. -
GeneratedScenario.rulesandUpdateScenarioRequest.addRulesareScenarioRule[](engine !319, #470), the shape the engine publishes:head(required) andantecedentsareScenarioRuleTerms (sort,features?,negated?,binding?) andcertaintyis optional (default0.8). They wereRecord<string, unknown>[]andunknown[], so a rule of the wrong shape (aconclusionin place ofhead) compiled and was refused only by the engine.addRulesis now mapped field by field, so a key outside the published shape no longer goes out; only the termfeaturesgo out as written. The otheradd*collections andGeneratedScenario.rulesgo out unchanged.
Removed — BREAKING
Section titled “Removed — BREAKING”platform.listBranchesandplatform.forkBranchare removed, with the typesConversationBranch,BranchListResponseandForkBranchRequest(flat and underPlatform). The engine removedGET /api/v1/branches/{conversation_id}andPOST /api/v1/branches/{branch_id}/fork(engine !308, #370); both answer404, and there is no replacement.SortDeletePreviewResponse.orphanedForeignChildrenis removed (engine !315, #435). A caller-chosen sort id can no longer name a sort outside the caller’s scope, so a delete preview no longer counts another tenant’s children.orphanedChildrenlists every child that would be left without a parent.
-
ingestion.searchWeb(request)(POST /api/v1/web/search, engine #413): searches the web through the deployment’s providers and returnsWebSearchResponse(query, the trimmed query, andresults: WebSearchHit[]of{ url, title, snippet, provider, score? }, best first). An emptyresultsis a search that found nothing. The hits are candidates foringestUrl; the search writes nothing for the tenant.limitis left out when unset (the engine refusesnull). A deployment with no provider answers503web_search_not_configured(ServiceUnavailableError, permanent:retryOn503retries it for nothing); failed providers answer502web_search_providers_failed. New types:WebSearchRequest,WebSearchResponse,WebSearchHit,WebSearchErrorCode. -
getWebCapabilitieslimits (engine #414):urlIngestion.maxCrawlDelayMs,maxRedirectsandrequestTimeoutMs, andwebSearch.maxResultsLimitandmaxQueryChars. The mapper dropped all five.UrlIngestionCapabilitydocuments the worst case of one crawl they bound. Code that builds aWebCapabilitiesResponseliteral must add the five fields. -
UrlIngestStoreFailedError(engine #415):ingestion.ingestUrlthrows it for the500web_ingest_store_failedanswer, when the crawl ran and storing its pages failed part way. It extendsInternalServerErrorand carriesreport(the pages stored, as a200would report them),failed(WebIngestStoreFailure:step,page?,message) andnotWritten(WebPageRef[]). Nothing is rolled back and a re-run is safe. Such a failure was a400 Domain errorbefore; a400now means a malformed body only. New types:IngestUrlStoreFailure,WebIngestStoreFailure,WebIngestStoreStep,WebPageRef,WebIngestErrorCode. -
conversation.submitAnswerFeedback(conversationId, request)(POST /api/v1/conversation/{id}/feedback, engine #84): rates one assistant answer-1,0or1(AnswerRating), with an optionalcorrectionandcomment(each at most 10,000 bytes), and returnsConversationAnswerFeedbackResponse(feedbackId,conversationId,turnId,rating,replaced). One rating per turn: a re-rating replaces it and keeps thefeedbackId. There is no delete; rate0to withdraw a judgement. A user turn is refused with422. An accepted rating is also recorded in the compliance ledger under/api/v1/conversation/answer-feedback. -
TurnDto.feedback(TurnFeedback:feedbackId,rating,correction?,comment?,recordedAt) on a rated assistant turn ofgetTurns. -
ConversationTrainingConfigDto.rewardUserFeedback(default0.5): a chat answer rated helpful earnsrewardUserFeedback × satisfaction; one rated unhelpful takes the failed-goal penalty. It is left out when unset (the engine refusesnull). -
RlTrainResponse.transcripts(RlTranscriptSummary:cognitiveAgent,chat,synthetic,ratedTurns) on a conversation run. A nonzerosyntheticwithuseRealConversationssays the tenant had no conversation to train on. -
verification.runVerification(request)(POST /api/v1/verification/run): discharges aSafetyPropertyagainst a neural network, an agent trace or a family of LLM outputs and returns aVerifyRunResult(certificateId,kind,method?,verdict). New types:VerifyRunRequest,SafetyProperty,VerificationTarget,OutputPredicate,AbstractIntervalVec,AbstractInterval,AbstractIntervalBound,TraceBundle,TraceProperty,AgentTraceStep,VerifyRunResult,VerdictKind,VerdictKindTag,ProofMethod,EmpiricalMethod. The PascalCase variant keys are sent verbatim. -
verification.health()(GET /api/v1/verification/health): the plain-text"ok". -
CertificateDetail.verdictDetail: the whole verdict, so the falsification witness and description, the empirical confidence and sample count, the residuation wake triggers and the proof are no longer dropped. -
Typed lifecycle refusal readers:
termRefusalOf(error)→TermRefusal(status,code: TermRefusalCode,message,termId,referrers,repairs,ruleIds,derivedBy?),ruleRefusalOf(error)→RuleRefusal(code: RuleRefusalCode,ruleId,derivations?,derivationsAtLeast?) andfunctionRefusalOf(error)→FunctionRefusal(code: FunctionRefusalCode,function,callers). They read the engine’sTermRefusalResponse,RuleRefusalResponseandFunctionRefusalResponseout of a thrownApiError, likesortDeleteRefusalOf.RuleNotWithdrawable.codeis typedRuleRefusalCode. -
AuthzTuple.etag:authz.writeTuplefolds theETagresponse header into the tuple; pass it asdeleteTuple(id, { ifMatch })for a stale-write-safe revoke (a stale tag throwsPreconditionFailedErrorwithcurrentEtag). -
visualization.termList(request?)(POST /api/v1/visualization/term-list) →TermListVisualizationResponse(graph,totalCount,offset,limit,emittedCount,coreferences?), andvisualization.unifyTerms(request)(POST /api/v1/visualization/unify-terms,op: 'unify' | 'anti_unify') →TermUnifyVisualizationResponse(graph,success,bindings?,resultTermId?). Both routes now publish their own components. -
ProofTraceNodeDto.substitutions: the variable bindings applied at a conversation proof step, keyed verbatim by variable name. -
SortRefSelectPropsandisSortRefSelectProps(props)(engine #172):ui.describerenders a feature declared by an entity range as aselectnode whosepropscarrysortName,valueKey: 'id'andlabelKey: 'label', and whoseload_dataaction lists the options. The guard narrows a node’spropswithout a cast. -
ReviewEntityOrigin,ReviewCandidateInput,ReviewAddErrorCode(review_sort_unknown,review_candidate_unknown,review_confidence_out_of_range,review_similarity_out_of_range, the422codes ofreviews.addPendingReview) andReExtractErrorCode(document_source_not_retained, the409ofreviews.reExtract).errorBodyCodereads both. -
ingestion.retryDlqEntry(id)(POST /api/v1/ingest/dlq/{id}/retry, engine #87): re-runs the entry’s whole document and answers202withDlqRetryResponse(entry, nowretrying;entriesRetrying;sessionId;documentId;statusUrl). Every pending entry of the document moves toretryingand settles tosucceededor back topending.404for an unknown or foreign id;409with aDlqRetryErrorCode(dlq_entry_not_pending,dlq_retries_exhausted,dlq_document_in_flight,dlq_source_not_retained);503without a background queue throwsIngestionQueueUnavailableError, assync: falseingestion does. -
IngestionStatsDto.alreadyIngestedandDocumentStatsDto.alreadyIngested(engine #387):truewhen the bytes were already ingested in the tenant. Nothing is re-extracted,documentTermIdnames the earlier registry document, and the request’scollectionPath/previousDocumentIdapply to that document. Code that builds either object by hand must add the field. -
IngestionSessionHandle.getDocumentStats(documentId)(GET /api/v1/ingest/sessions/{session_id}/documents/{document_id}/stats): one document’s stats,nullwhile it still runs (204);404and409(failed or cancelled) throw. The engine publishesDocumentStatsDtoas the200body (engine #464). -
DocumentVersionErrorCode(engine #387):ingestMarkdownandingestDocumentanswer404previous_document_not_found(NotFoundError) for apreviousDocumentIdthat names no document, which was silently dropped before, and409duplicate_document_version_conflict(ConstraintViolationError) when duplicate bytes already belong to another version family. Withsync: falsethe same refusal ends the session documentfailed. Read the code witherrorBodyCode. -
ontology.listUpperOntologies()(GET /api/v1/ontology/upper, engine !297): every vendored upper ontology, in registry order, asUpperOntologySeedsResponse(seeds: UpperOntologySeed[]ofname,title,ontologyIri,versionIri,license(SPDX),sourceUrl,retrievedOn,byteLength). A seed is third-party data served unmodified under its licence.ontology.getUpperOntology(name)now takes any listed name (string);UpperOntologyNamestays as the set this SDK version knows. Neither read registers a tenant. -
sorts.patchSortFeatureamends a declaration’sdescriptionandindex(engine !305, #389).PatchSortFeatureRequestis now the unionPatchSortFeatureRename(name,cascadeTerms?) |PatchSortFeatureAmendment(descriptionand/orindex). Per key, an absent key keeps,nullclears, a value replaces;index: nullreturns the declaration to silence, which is notnot_indexed. This is the only way to clear either field. The union makes the engine’s new 400s (a rename combined with an amendment,cascadeTermson an amendment, an empty body) compile errors; a blank description is still a 400. The alias change is listed under Changed — BREAKING. -
OsfqlFailure.atomicRefusalandOsfqlError.atomicRefusal(engine #365): the code of a program refused because aDROP SORT/ALTER SORTis not alone. -
EnrichedHealthResponse.bootId,startedAtanduptimeSeconds(engine !306, #287) onhealth.check(): the identity of the engine process. A newbootIdmeans the engine restarted and every derived conclusion a client cached is reset. They are optional only because a gateway can answer with an empty or partial body; the mapper never invents them.BuildInfoDto.versionis now the engine’sgit describewithoutvand-dirty(0.1.150-76-g8cb95b7c8), andgitHash/buildTimestampare present in development builds too. -
ArgumentLabelandArgumentAttackKind(flat and underArgumentation). -
ConversationKind,RecordableConversationKindandListConversationsQuery(engine !310), flat and underConversation. -
TurnDto.proofTrace,reasoningTrace,validityCertificateId,validityCertificateHash,validityCertificateRuleProgramHashandvalidityCertificateRuleRevision(engine !310): a reloaded assistant turn ofgetTurnscarries the proof tree, the reasoning trace and the certificate the live answer carried. -
ingestion.getDocumentStats(sessionId, documentId)(GET /api/v1/ingest/sessions/{id}/documents/{doc}/stats, engine !311, #400): the stats of one document, ornullwhile it runs (204). An RDF document is aNotFoundError(its receipt is ongetSession); a failed or cancelled document is aConstraintViolationError. -
IngestionSessionHandle.waitForSession(options?)(engine #400): polls likewaitForCompletionand returns the completedIngestionSessionDetailResponse, whose documents carry their receipts. Use it for aningestRdfsession queued withsync: false: it has no markdown stats. -
IngestionSessionDetailResponse,SessionDocument,IngestionDocumentStatus,DocumentReceiptandRdfImportReceipt(engine #400):documents[].errorgives the reason a document failed, anddocuments[].receiptwhat a completed one wrote, tagged bykind(markdown: the document stats;rdf:sortsCreated,termsCreated,shapesParsed,repairedIriCount,repairedIris). -
BulkCreateSortsRequest.prefixes(engine !311, #404, #454): prefix bindings (prefix → namespace IRI,''= the default prefix) for the batch’s CURIE names. The keys go out verbatim. PasstranslateRdf(...).prefixes. A malformed binding is aBadRequestError; rebinding a bound prefix is aConstraintViolationError. -
BulkSortErrorKindadds'unbound_prefix'and'non_canonical_prefix'(both withsortCreated: false). An exhaustiveswitchover the kind must handle them; prefersortCreated. -
DocumentTypeadds'text'(engine #405):ingestDocumentandingestDocumentBatchaccept base64.txt,.mdand.markdownfiles, and the parse stats name thetextparser. A file that is not UTF-8 is a422oningestDocumentand oningestDocumentBatch({ sync: false }), and a failedresultsentry on an inline batch; either way the message names the file and the byte offset. -
engine.getStats()(GET /api/v1/engine/stats, engine !314, #93), alsosystem.engine: the caller’s tenant’stermCount,ruleCount,tenantSortCountandsharedSortCount(withtenantId), each equal to the total of the listing it summarises. NewEngineClientand typesEngineStatsResponse(flat and under the newEnginenamespace). -
reasoning.listOperations({ limit? })(GET /api/v1/reasoning/operations, engine !314, #93): the subsumption, merge, entailment and disentailment verdicts the engine answered for the tenant, newest first, asReasoningOperationListResponse(operations: ReasoningOperation[]ofsequence,name,goal,goalTruncated,verdict,degree?,computationTimeMs,createdAt;total;retentionofscope: 'process',capacity,goalBudgetBytes). The log is process-scoped and bounded;limit: 0is aBadRequestError. New types:ReasoningOperationName,ReasoningVerdict,ReasoningOperationLogScope,ReasoningOperationRetention. -
RuleTermWriteError(engine !312, #376):terms.updateTerm,terms.deleteTermandinference.bulkRetractTermsthrow it for the409write_targets_ruleanswer — a write onto a rule’s own term (its conclusion template, an antecedent pattern or a variable). It extendsConstraintViolationErrorand carriesruleIdsandrepairs(replace_ruleandwithdraw_ruleper rule the caller may edit; none for a plugin, system or bootstrap rule). A pattern or variable delete answeredterm_named_by_rulesbefore; a conclusion edit or delete answered200/204.bulkRetractTermsremoves nothing.OsfqlRepair.kind/DerivedFactRepair.kinddocumentreplace_rule. -
SortRefFeatureValue({ sort_ref, sort_name? }) inFeatureValueDto(engine !312, #353): a sort named as a value in a listed rule, a chaining answer or a fact listing. It arrived as{ term_id }before, indistinguishable from a term.sort_nameis absent when the caller’s lattice does not hold the sort.FeatureValueDtoalso gainsInlineTermFeatureValue, the whole Ψ-term the engine inlines for a hidden rule template. New helpers:isSortRefFeatureValue(value)andfeatureValueToInput(value), which converts a listed value into the write format ({ sortRef },{ termId }, the literal’s scalar) so a rule read fromgetRules()can be sent back toreplaceRule. -
ExplorationStatusResponse.question(engine #410): the pending question,nullonce complete — a client that lost an answer re-reads it here.ExplorationQuestion.questionId,ConfirmRequestandAttributeExplorationErrorCode('exploration_question_not_pending', flat and underAnalysis). -
OsfqlResponse.computationTimeMs(engine !314, #93): the engine’s own run time of the program. Code that builds anOsfqlResponseliteral must add it. -
ConformanceRepairResponse.durabilityFailures(engine !314, #406): the repairs applied in memory but not persisted; empty on a clean pass. Code that builds the response by hand must add it. -
UpdateScenarioResponse.warnings(engine !312, #403): a scenario rule the engine refused is named here (rules[i] (conclusion `s`) was not written: <reason>), not thrown. Code that builds the response by hand must add it. -
DocumentResult.evaluatedText(DocumentEvaluatedText:evaluatedChars,totalChars,truncated) andDocumentRuleResult.withheldStatus(engine !314, #411) ondocuments.analyze: how much of the document the rules pass read, and the pass/fail the engine withheld (reportedinconclusive) because it was not grounded in the text. -
WaitingConditionDto.variable(the awaited variable, e.g.?y) andResiduatedTermDto.resolvedAt(epoch ms) onvisualization.residuationStateandexecution.residuate(engine #93). -
createApiErroris exported from the package root (sdk#51). Theerrorevent ofconversation.sendMessageStreamdocumented it, but only the error classes were exported.createApiError(event.status, { error: event.error, message: event.error }, new Headers())gives the same subclass,messageanderrorCodethatsendMessagethrows for the same failure (InternalServerErrorfor502,ServiceUnavailableErrorfor503, …). Anerrorevent withoutstatusis not an HTTP failure. The stream still yields the event and does not throw it. -
ResponseShapeError(sdk#54): every resource method that reads a JSON body throws it for a2xxresponse it cannot read, in place of the bareTypeErrora normalizer raised (Cannot read properties of undefined (reading 'map')). It extendsReasoningLayerError, notApiError, and carriesstatus,method,path(after the base URL, without the query),bodyandfield: the dotted path of the first published field the body lacks, holds asnullor holds with the wrong kind (items,items[3].sort_suggestions).fieldisundefinedwhen the body is not JSON (an HTML page served with200;causeis the parse error) or when every published field is present. The check runs only after reading the body failed, so a body without a field the SDK defaults for an older engine is still read. Any other error a normalizer throws (ValidationError,ReasoningLayerError) is unchanged. -
Sorts.SortCreateErrorCode(sort_id_unavailable,sort_id_held_under_another_name; engine !315, #435): the409codessorts.createSortanswers for a caller-chosenid.sort_id_unavailableis an id held outside the caller’s scope (another tenant’s sort or the shared lattice), or a version 5 UUID that names none of the caller’s own sorts;sort_id_held_under_another_nameis an own id posted under a differentname(rename withpatchSort). Read the code witherrorBodyCode. -
DocumentRuleResult.basis(DocumentVerdictBasis:quoted_violation,quoted_compliance,stated_unknown,absence) andDocumentRuleResult.withheldConfidenceondocuments.analyze(engine !315, #411): what the evaluator said a verdict rests on, and its confidence in a withheld verdict. Both are absent from an older engine. -
Documents.DocumentAnalysisErrorCode(llm_answer_unparseable; engine !315, #411):documents.analyzethrows anInternalServerErrorwith status502and this code when the LLM answered a step with no readable JSON. -
Terms.TermWriteErrorCode(write_creates_rule; engine !315, #455):terms.createTerm,terms.updateTermandinference.addFactanswer422with this code for a non-empty list under the featurewhen, which would make the term a rule.bulkCreateTermsnames such a row inBulkRefusedError.rows(feature: 'when'), andbulkAddFactslists it inrejected. OSFQL writes answer the refusal codewrite_creates_rulewith the repairuse_derive. -
OsfqlPreviewStatement.refusal(engine #449, #457): the refusal the run would raise for the statement, with the messageosfql.executeanswers: a stand-alone write naming an earlier statement’s variable, or a write onto an append-only fluent term, an inlineMATCHwrite included. -
GenerateOntologyRequest.languageonontology.generate: the language of the generated human-readable text, as a BCP-47 tag or a language name. Sort and feature names stay English snake_case; absent, the model writes in the language of the prompt. -
FeatureInputInlineTerm.bindingandFeatureInputInlineTermByName.binding(engine #438): a nested inline term names its own identity. On theclauseof a negation it names the term the negation is about:not(bind('?T', psi('meat_topping')))isNOT EXISTS ?T: meat_topping(), and a positive antecedent must bind?T. -
prefixesonscenarios.create,materializeScenarioandupdate(CreateScenarioRequest,MaterializeScenarioRequest,UpdateScenarioRequest; engine !319, #470): prefix bindings (prefix → namespace IRI,''= the default prefix) for the scenario’s CURIE names, with the contract ofBulkCreateSortsRequest.prefixes. They are bound in the same hold of the lattice as the scenario’s sorts and released if the scenario is refused. The keys go out verbatim. PasstranslateRdf(...).prefixes. A malformed binding is aBadRequestError; rebinding a bound prefix is aConstraintViolationError. -
ScenarioRuleandScenarioRuleTerm(underScenarios): the typed scenario rule, with the antecedentbindingthe engine now publishes.
Changed
Section titled “Changed”-
documents.analyze(engine #411): a withheld verdict reportsconfidence: 0, and the evaluator’s value moves towithheldConfidence.overallComplianceno longer countsnot_applicablerules, so the number changes even where it is notnull.documents.checkadds a warning for a document on which the rules pass evaluated no applicable rule, and reports an unreadable LLM answer as its non-fatal KB-rules warning. -
Review decisions change the knowledge base (engine !304):
rejectEntityretracts a provisional entity with its edges,mergeEntitymoves the edges and retracts,correctEntitypatches the live term, andreExtractnow runs: it creates a new document version and answers404for an unknown document. -
Every UI action is atomic (engine #172): a failure keeps nothing, and its
errorends with(the program was rolled back: …). -
Saved goals live outside the fact store (engine !306, #288).
inference.createGoal,backwardChain/fuzzyProve/nafProve/bayesianPredictwithsaveGoal: true, andbulkFuzzyProvewithsaveGoals: truestore the goal apart from the tenant’s facts: it no longer appears in aMATCHor ingetFacts(), andcreateGoal/bayesianPredictno longer write it into the live store. A sort-onlybackwardChaingoal withsaveGoal: truenow answers a UUIDgoalId(it wasnull, and the save was dropped).nafProvewithsaveGoal: truealso saves the goal’s own term. Goals that an earlier engine saved are stray facts in the fact store andlistGoalsdoes not list them: wipe them (for example withadmin.clearMyTenantData()). -
Reads never register a tenant (engine !306, #385).
GET,HEADandOPTIONSnever create the tenant’s registry row; a served unsafe-method request (POST/PUT/PATCH/DELETE) or a WebSocket upgrade does, but a401/403/404/405/501answer never does. After reads only,admin.getMyTenant()andadmin.deleteMyTenant()answer404.osfql.preview,osfql.diagnose,argumentation.evaluateandingestion.searchWebnever register the caller, their400/415/422answers included. -
The unified inbox and the DM pairing state are durable (engine !308, #358): DM policies, allowlists, pending pairing requests and inbox items are stored in PostgreSQL and survive a restart. A tenant clear or delete, or a factory reset, erases them and counts them in
inboxItemsDeleted/dmPairingStateDeleted. -
A tenant delete or clear releases the tenant’s channel routes (engine !308, #369): another tenant’s
channels.registerRoutefor such an account now succeeds instead of answering409. -
argumentation.evaluate(engine !306, #76): the request body is closed (an undefined key is a plain-text422); a preference or retraction naming no claim is a400(preference names no claim: "x"/retracted names no claim: "x"); a preference on a retracted claim is dropped with a warning.Argument.textis the claim’s prose, else the OSFQL pattern of the stored fact, else its term label;Argument.indexexcludes retracted claims;ArgumentQuestion.promptuses typographic quotes (Which is true: “A” or “B”?); a rule-derived attack’sreasonnames the rendered fact. -
coherence.analyze(engine !306, #76):CoherenceDerivationStep.derivedFactLabel, the rule-derived clash reason and the conflict-fact diagnostic carry the rendered OSFQL pattern (for exampleincoherence(motif: "two colours")), notterm-xxxxxxxx. -
platform.runDiagnostics(engine !306, #368):checksholds three entries,memory_usage,system_clockanddisk_access, each once (it held four), sopassCountis3on a healthy engine. -
sendMessageandrecordTurnraiseConstraintViolationError(409) for a conversation another surface owns, andsendMessageStreamyields anerrorevent withstatus: 409(404for an unknown conversation) (engine !310): the stream checks the conversation after it opens.sendMessageraisesNotFoundErrorfor an unknown conversation. The certify-or-abstain answer is now stored and carriesturnId. On a rescued turn,suggestions[0]is theNo exact matchnotice, andoriginalOsfqlis always set whenrepairedistrue. -
ui.generaterecords agenerative_uiconversation titled by the prompt (engine !310), listed bylistConversations({ kind: 'generative_ui' }). AconversationIdthe caller does not own is aNotFoundError; a conversation of another kind is aConstraintViolationError. The engine checks both before the LLM runs. -
ingestAudioappliesingestionConfig(engine !311, #367), like/ingest/markdown, unlessparseOnlyis set. Without a config, the extraction strategy is the request default (LLM), not the deployment’sEXTRACTION_STRATEGY. -
Markdown ingestion has no 100,000-byte limit (engine #405):
ingestMarkdown,ingestMarkdownBatchandresumeIngestionare bounded only by the 50 MiB body limit (413, anApiError). -
OSFQL binds prefixes and reads CURIEs (engine #404):
PREFIX ex: <iri>;binds a tenant prefix durably, andMATCH :Pizza(:size: ?S);reads unquoted CURIE names. A rebind, or aDEFINEof a CURIE whose prefix is not bound, is anOsfqlErrorexecutionfailure (422).sorts.createSort,patchSortandpatchSortFeatureaccept a CURIE under a bound prefix, and refuse an unbound one with400.sorts.getSchemastarts with the tenant’sPREFIXlines. -
TranslateRdfResponse.prefixesis the binding map the names are compacted through: a document prefix that conflicts with a tenant binding is renamed, never rebound.termCountno longer counts stub terms for the objects of skipped schema declarations. -
IngestRdfResponse.sortsCreatedcounts the sorts the OWL pass created (0withoutparseOntology), not the lattice size. -
A synchronous
MATCH … SEMANTICrow carries its feature columns in rank order. A binder-less pattern keeps_semantic; a pattern with a named root has no_semanticcolumn. Ranking obeys visibility and the bitemporal slice. -
query.nlQueryin cognitive mode filters by every translated constraint, andgeneratedOsfis the executed OSFQL. A constraint it cannot carry issuccess: falsewith the reason inerror; an emptyresultswithsuccess: truemeans no match. A filtered or structured question over a rule sort that has members only by derivation issuccess: false, and so is an aggregate over any sort a rule concludes (the aggregate reads only stored members); the reason is inerror.NlQueryResultItem.idis the matched term id. -
A rule head no longer carries
certaintyinfeatures(engine !312, #377, #391): readRuleEntryDto.certainty. A fact’s authoredcertaintyfeature now shows indisplay. Everydisplay/goalDisplay/ruleLabeltext shows all data features sorted by name, with JSON-escaped strings, shortest round-trip reals and full lists (no[...N items]);ruleLabelno longer shows the aggregator. -
A
MATCHwith an inline write classifies by that write (engine !313, #416): an inlineINSERT,LINKorUPSERT LINKisadditive_write; an inlineUPDATE,REMOVE,UNLINK,DELETEorDETACH DELETE(or anIF/WITHthat holds one) istargeted_destructive.ui.executeActionnow needsconfirm: truefor those (elserequiresConfirmation: true), its dry run answersaffectedPreview.exact: false, andosfql.previewanswersaffectedErrorand noaffectedfor an inline destructiveMATCH. A writing query in aui.describe,ui.generateor AG-UI data load is refused without an exception:stats.osfqlExecutedstarts withFAILED:. -
generation.generateDocument(engine !313, #360) is read-only (no derived fact is written) and covers only the root’s material (the terms it reaches plus the terms that reference it). A root that is absent, an engine built-in or rule scaffolding answers404(NotFoundError) before the LLM check; more than 500 terms of material answers422. -
fuzzy.searchTopK(engine !313, #378):exhaustive: truewith nocoveragewhen no retrieval cap fired;exhaustive: falsewithcoveragewhen one did. -
memory.remember(engine !313, #379): the tenant route no longer refuses text that contains code; the remaining400s are an empty text, more than 500 characters and an importance outside0..1. -
Agent-queued action reviews (engine !313, #380) carry
agentIdandgoalContextId, soactionReviews.list({ agentId })finds them; approving one re-runs the agent’s integrated cycle. -
Scenarios (engine !312, #403):
scenarios.create,materializeScenarioandupdatedecode rule features with the REST rule encoding ("?X",{"name":"?X"},{"_var":"?X"}are variables; a literal starting with?is{"type":"String","value":"?x"}). Each rule runs thePOST /api/v1/inference/ruleschecks; a refused rule is not written and is named inwarnings. An unknown sort is refused, not widened tothing. -
conformance.repair(engine !314, #406): an applied repair is durable — visible onGET /terms, kept across a restart, versioned in the history and announced on CDC. -
The residuation listing (engine #93) keeps one row per term (
idis the term id), keeps completed and failed rows, fillswaitingFor, applies both filters and reportscreatedAtin epoch milliseconds.GET /terms/{id}reportsfailedwhen a later check failed. -
osfql.previewcounts the terms an inlineMATCH … UPDATE / REMOVE / UNLINK / DELETEchanges (engine #457). Such a statement carriesaffected(count,exact,sampleRows, one row per changed term) like aRETRACT, where it carriedaffectedErrorand no count. A term that several joined rows bind counts once, and anUNLINKcounts a source only when it holds the edge.ui.executeActiondry runs report the same exact count. -
ingestion.getDocumentStatsand the session handle’sgetDocumentStatscall the generated route: the engine now publishes theDocumentStatsDtobody of its200(engine !317, #464). -
ui.generatebinds a data query to its rows’ term ids (engine #471): a certified single-pattern data query gets the identity binding on its root pattern (noted indiagnostics), and the view that uses it names the id column inprops.rowKey. Any other query names norowKey. -
scripts/check-spec-validity.jsfails on a root tag declared more than once (sdk#2). The engine declarespreferencesonce since #465.
-
Every resource method that reads a JSON
2xxbody goes throughdecodeResponse(sdk#54). Twenty-two pass-through methods (statistical,causal,neuro-symbolic,cognitive,collections,control,proof-engine,snapshots) returnedresponse.dataraw, so a gateway’s200HTML page resolvednulltyped as the DTO. They now throwResponseShapeError.pnpm run check-response-decodeflags a barereturn response.data;unless the route asks fortext,bloborarrayBuffer. -
ResponseShapeErrormessages use the right article:not an array (got an object), not(got a object). -
fuzzy.predictEffectcalls the generated route instead of a raw request. -
ingestion.retryDlqEntrynever retries the queueless503, even withretryOn503, like thesync: falsepaths. It retried it up tomaxRetriestimes before it threwIngestionQueueUnavailableError. -
ingestion.ingestUrlraisesUrlIngestStoreFailedErroronly for a500body that has the complete published shape (anerrorstring, and pages and skipped entries with their required keys). A body withreport.pages: [null]threw a bareTypeErrorfrom the mapper and lost the originalInternalServerError; it now stays thatInternalServerError. -
featureValueToInputkeeps a listed string that starts with?a constant: it returns{ type: 'String', value }. Returned bare, the engine’s rule write path read"?x"as a variable, so a rebuilt rule matched every value. -
Generation
GenerationVerificationDto.violationspassed the wire objects through raw, sorelatedFactIdwas alwaysundefined(the snake_caserelated_fact_idleaked instead). Each violation is now mapped. -
A conversation proof node with a
nulldetail,ruleorconfidencereadsundefined, like every other absent optional field. -
ui.generatereads anullcertificate_id,page_version,conversation_id,data,layout,descriptor,trace, intentstatement_id/returns/reasonor data-sourcereturnsas absent. It now calls the generated route against the publishedUIGenerateResponseinstead of a raw request typed by hand-written wire shapes. -
coherence.analyzecalls the generated route, typed against the publishedCoherenceReportinstead of hand-written wire shapes. Anulloptional report field (subsumption,clash,advisory,target,weakest_boundary, …) stays absent, as an omitted one does. -
marketplace.searchCatalogandconformance.checkare typed against their own published components (MarketplaceSearchRequest/MarketplaceSearchResponse,ConformanceViolationDto) instead of hand-written wire shapes;searchCatalogcalls the generated route. -
reviews.addPendingReviewcalls the generated route, and a pending review’s sort suggestions are typed against the publishedReviewSortSuggestionDto. A malformed suggestion is no longer refused with aValidationErrorat read time; the published DTO is the contract. -
ConstraintViolationError.termId,.featureand.constraintare read from the engine’s flat 409 body (ConstraintViolationResponse:{ error, message, term_id, feature, constraint }). The SDK read them from adetailsobject no engine sends, so all three were alwaysundefined. -
Verification errors: the engine now answers
{ error: <reason phrase>, message: <text> }, soApiError.messageis the engine’s sentence (it was the synthesizedHTTP 404) anderrorCodethe reason phrase.verification.getCertificatecalls the published route; a non-UUID id is aBadRequestError(400), an unknown id aNotFoundError(404). -
sorts.patchSortFeaturesent onlynameandcascade_terms: the mapper droppeddescriptionandindex. It now copies every key that is set, and sendsnullasnull. -
OsfqlFailureFromApiToFrontdropped the error body’satomic_refusal; it now reads it, and refuses a malformed one like the other details. -
discriminateFeatureValuethrew on every object, so it could not read a variable, a literal wrapper or a term reference. It now narrows all five object arms (var_name,literal, an inlined term,sort_ref,term_id). -
inference.bulkAddRulesdropped a row’saggregator; it now forwards it, asaddRuledoes. -
sparql.translateQueryandtranslateUpdateare now typed against the publishedSparqlExplainDto/SparqlUpdateExplainDto(engine !317, #464), and a plan node’sattrskeeps only string entries.
Documentation
Section titled “Documentation”- UI actions (engine #172): a
"sort_ref"field takes a term UUID invalues, which the engine stores as a Reference; a non-UUID, an undeclared entity range, or a non-UUIDsort_refplaceholder in arun_osfql/ feature-baseddelete_recordtemplate answers400(BadRequestError), and a UUID that names no visible term answerssuccess: false.UIAssemblyStatsDto.includedFeaturescounts only fields with a value control. - Review decisions (engine !304):
approveEntity,correctEntity,mergeEntityandrejectEntitydocument403for a reserved or unwritable sort;rejectEntity,mergeEntity,bulkRejectandbulkMergedocument the503for terms that could not be made resident. Each method documents its knowledge-base effect, and thatnotesandreasonare kept as the decision note. functions.registerFunction/replaceFunction(engine !314, #388) document the422codomain codesfunction_codomain_empty,function_codomain_non_string_label,function_codomain_blank_labelandfunction_codomain_repeats_label, checked before theconformalTaucheck;evaluateFunctiondocuments the error for a stored mixed codomain such as["a", 3]. ANeuralcodomain must be non-blank, distinct string labels.- The
CheckDocumentResponse.warningsofdocuments/checkincludeKB rules pass read only the first N of M characters of <name>; …for a partly read document, andkbRuleReportcarries the sameevaluated_text/withheld_statusfields. - The Ψ-terms concept page documents the read format of a listed rule’s features and the
featureValueToInputround trip; the workshop guide says that a rule needs at least one antecedent. ConversationTrainingConfigDto: withuseRealConversationsa conversation run now trains on the cognitive agent’s conversations and the tenant’s chat conversations together,numTranscriptsof each (selected byconversationIdsandagentId), and falls back to synthetic transcripts only when neither source has one (engine #84). The field docs say so.ingestion.ingestUrldocuments the redirect cap asurlIngestion.maxRedirects, and an unknown body key as422, not400.- WASM plugin methods document
500(plugin store failure) beside403/404/501. rag.ontologyRagdocuments the422an unknownfeatureConfigkey now answers (the engine reads it withdeny_unknown_fields); the SDK sends only the seven declared keys.- Term writes (engine #172): a scalar literal in a feature whose declared range is an entity sort is refused with
422onterms.createTerm,updateTerm,bulkCreateTerms(aBulkRefusedErrorrow), OSFQL writes (OsfqlError,execution),actions.applyActionandinference.addFact. WriteValue.reference(termId). The terms guide documents the OSFQL nested designator<sort>(__osf_term_id: "<id>"), which references an existing term and mints nothing. CreateSortRequest.parentsandsorts.createSort(engine !305, #355): a parent id must name a sort of the caller’s tenant or a shared system sort. Another tenant’s sort id is a400parent sort(s) do not exist, byte-identical to an unknown id (it was accepted with201).FeatureDescriptorDto.cardinalityOrigin(engine !305, #290):createSortandbulkCreateSortsacceptinferredonly as an unchanged echo of a bound the tenant already stores as inferred on the sort or an ancestor, with the sort’s own record declaring none; anything else is a400/unsatisfiable_declarationthat startssort 'S': feature 'F':. OSFQL@card(0, n) required(orrequired @card(0)) is aparseOsfqlErroron execute and preview; it was stored as a minimum of 1.inference.forwardChain/ForwardChainRequest.keepDerived(engine #290): a deadline-cutkeepDerived: falserun (200or504) leaves nothing stored; a deadline-cutkeepDerived: truerun records provenance for every conclusion it stored and announces it.sorts.deleteSort: thesort_would_orphan_childrenmessage now endsPass ?descendants=delete to delete them with it.; an OSFQLDROP SORTrefusal names the OSFQL clauses (CASCADE TERMS,ORPHAN RULES,CASCADE DESCENDANTS) and the sort (engine #374). Messages are carried verbatim.OsfqlPreviewStatement.sorts(engine #412): a feature-type word of aDEFINE(string,integer, …) is listed only when a sort of that name exists in the program or the tenant.FeatureDescriptorDto.description/indexpoint atpatchSortFeaturewithnullas the way to clear them.- Tenant erasure (engine !308, #381):
admin.clearAllData,clearTenantData,clearMyTenantData,deleteTenantanddeleteMyTenantdocument the500(InternalServerError) whose message containsuploaded source files were not erased: the wipe is incomplete, so retry it. A delete that fails this way writes no tombstone, so the tenant stays live. - Channels, cron, inbox and pairing (engine !308, #358) document their new
500s:channels.receiveWebhookwhen the DM policy or inbox store cannot be reached (the message was not admitted, so the relay must deliver it again),cron.runNowwhen the job fired but its inbox item was not stored (the run is not undone),inbox.list/markReadwhen the inbox store cannot be reached, and everypairingmethod when the pairing store cannot be reached. authz.writeTupleandauthz.deleteTuple(engine !306, #371): a caller that is not exempt gets403in every case, before any validation. For an exempt caller,deleteTupleanswers404for a missing id or for an id that names a term that is not acan/has_roletuple (the term survives), also withifMatch.inference.deleteGoaldocuments the404for an unknown goal and what the delete removes;createGoal,listGoalsand thegoalIdfields say that a saved goal is not a fact.sorts.createSortandCreateSortRequest.id(engine !315, #435): anidmust be unused or name one of the caller’s own sorts; a re-post of an own id updates that sort and keeps its name. The shared-lattice id the engine derives from the samenameis a400(BadRequestError), not a409; only a shared-lattice id posted under another name issort_id_unavailable.terms.createTermandterms.updateTermdocumentNamedConstraintViolationErrorfor a422refusal by a named constraint: both routes now publishTermWriteUnprocessableBody(DeclarationViolationResponse | CodedErrorResponse | ErrorResponse) as the422body, wherePUT /api/v1/terms/{id}published onlyErrorResponsebefore (engine !315/!317).DocumentVerdictBasis: apassorfailwhose basis does not agree with it (astated_unknownor missing basis included) is withheld and reportedinconclusive.terms.updateTerm,terms.deleteTermandRuleTermWriteError(engine #456): a ground sub-term that rule authoring minted inside a rule is part of the rule, so a write onto it is refused409write_targets_rule;getTermanswers404for it andtermExistsanswersfalse.terms.createTerm,updateTermandinference.addFactdocument the422for an engine-owned feature such as_derived_by(engine #249).analysis.refuteImplicationandRefuteRequest.features(engine #462): a counterexample must also respect every confirmed implication, else400. The exploration poses one question for each pseudo-intent.scenarios.materializeScenarioandscenarios.update(engine !319, #470) document the400(BadRequestError) for a minted sort, feature, fluent, computed feature or axiompropertyname the creation gate ofsorts.createSortrefuses (a name OSFQL cannot read, or a CURIE whose prefix the tenant does not bind canonically); nothing is written.scenarios.createanswers the same case with a422Generated scenario refused: …. An inferred relation edge name the gate refuses is skipped and named inwarnings.scenarios.createandmaterializeScenario: a sort a rule names that the scenario does not declare is created underthing, not refused. The JSDoc said the opposite; onlyupdate’saddRulesrefuses an unknown sort.ui.describe(engine #471) documents the400for atermIdthat is not a UUID and the404for atermIdthat names no term of the sort; adetailorformview withtermIdloads that term’s own values. A data view names inprops.rowKeythe column in which each loaded row carries its term’s id:__osf_term_idfor the engine-generated query, and the query’s own identity variable for a customquery. A custom query whose rows cannot carry an id (an aggregate, aWITH, a projection without it, aDISTINCT, an inline write) names norowKey. Read the columnrowKeynames; do not hard-code__osf_term_id.UIDataPayload.termIdslists the terms the load program produced; it is not one id per row.ui.executeActionandUIActionRequest.sortName(engine #471):submit_form,update_formanddelete_recordresolve the sort by its exact name, so a CURIE sort works. A400now answers an unknown sort, a sort name with a backtick, a value key the sort does not declare, and anupdate_formwhose term is held under another sort.TranslateRdfResponse.prefixespoints atMaterializeScenarioRequest.prefixestoo.
Known engine gaps (filed)
Section titled “Known engine gaps (filed)”- reasoninglayer#473:
terms.getTerm(ruleId)answers404for a rule since engine #456, so the way theinference.deleteRuleandreplaceRuledocs give to read a rule’sIf-Matchtag does not work. The contract test for a staleifMatchon a rule delete fails against engine5cef4fa34for this reason.
[3.0.1] - 2026-09-28
Section titled “[3.0.1] - 2026-09-28”Documentation
Section titled “Documentation”- Corrects the list of resolved issues in the 3.0.0 entry below. No code change.
[3.0.0] - 2026-09-28
Section titled “[3.0.0] - 2026-09-28”Brings the SDK in line with engine main 6acb754ca (2026-09-27). The vendored spec went from 756 to 815 operations and from 1877 to 1996 schemas; every new operation is wrapped. Resolves SDK#17–#44 and #46–#48.
The engine carries no compatibility paths: old wire shapes are refused with 422, not aliased. This release therefore targets the current engine only. An SDK 2.x client talking to this engine breaks where the list below says so, and an SDK 3.0 client does not support an older engine.
Migrating to 3.0.0
Section titled “Migrating to 3.0.0”- Path parameters are percent-encoded by the SDK, exactly once. Stop pre-encoding ids (
encodeURIComponent(sourceId)), or they are encoded twice (#38). - Opaque JSON is sent verbatim.
sources.register()config,scenarios.materializeScenario()/update(), webhook action inputs and outputs, agent message content,actionReviews.modify()modifiedParams, oversight stepinput/output, UI customizations and action values,actions.applyActioninputs,compliance.appendextraand review features are no longer snake_cased. Write the engine’s own key spellings (#17, #39). osfql.execute/osfql.previewthrowOsfqlErrorfor every OSFQL error body, and every engine deadline (504,error_type: "timeout") is aDeadlineExceededError. A 400 parse error is no longer aBadRequestError: branch onerror.errorType(#43).sync: falseingestion returns anIngestionOutcome, not a bare session. Readoutcome.kind(#22).ReviewsClientreturns typed camelCase objects, not the raw snake_case body typedunknown(#30).- Constructed fixtures need the new required fields listed below. Reading code is unaffected.
Changed — BREAKING
Section titled “Changed — BREAKING”Transport and errors
- Every path parameter of every generated route, and of the hand-written paths, is percent-encoded once (#38).
ApiError.messageis the body’smessagefor bodies with noerrorfield (OSFQL, SPARQL, property graph). It wasHTTP <status>.- HTTP 503 is
ServiceUnavailableError, a subclass ofInternalServerError.instanceof InternalServerErrorstill matches. - HTTP 504 with
error_type: "timeout"isDeadlineExceededErroron every route (wasInternalServerError). - HTTP 409
write_targets_derived_factisDerivedFactWriteError(aConstraintViolationError); 412 isPreconditionFailedError; 428 isPreconditionRequiredError. ValidationError.fieldpaths name their container:rules[k].term,rules[k].antecedents[i],literals[i].term,goal,antecedent,consequent(#48).- A raw
TermInputDtofeature value that matches no variant throwsValidationErrorbefore the request is sent. It was sent as{}(#47).
Sources, scenarios, opaque bodies
sources.register()sendsconfigverbatim; write engine keys (connection_string,file_path, …) (#17).scenarios.materializeScenario()/update()send the scenario verbatim.MaterializeScenarioRequest.scenariois a typedGeneratedScenariowhose keys are the engine’s own, mixed spellings (curriculum_stages,guardConstraints, …). A camelCasecurriculumStagesis ignored by the engine (#39).webhookActions.register/invoke/completeInvocation,cognitive.sendMessage/broadcastMessage,actionReviews.modify,oversight.judge/refine/createSession/ingestStep,ui.describe,ui.executeAction,actions.applyAction,compliance.append,reviews.addPendingReviewandreviews.correctEntitysend the caller’s nested JSON verbatim. Keys incustomizations,values,originalValues,inputs,extra, reviewfeaturesand the like are no longer snake_cased.DiscoveredFeatureDto.constraintsis a taggedDiscoveredConstraintDtounion (was the engine’s Rust debug string).
Ingestion (#20, #21, #22)
ingestMarkdown(req, { sync: false })andingestDocument(req, { sync: false })returnIngestionOutcome('queued'→session,'inline'→result).ingestMarkdownBatch(req)returnsIngestionOutcome<IngestionBatchSession, IngestMarkdownResponse>;ingestDocumentBatch(req, { sync: false })returns anIngestionBatchSession(documentIds,documentCount).ingestRdfdefaults to synchronous ingestion.IngestRdfRequestlosesownerId,tenantIdandsync; pass{ sync: false }as the second argument.- A
?sync=falsedocument or batch refused by a deployment with no queue throwsIngestionQueueUnavailableError(status 503), and is never retried, even withretryOn503. IngestionSession’s polling methods moved to the baseIngestionSessionHandle; batch handles are notinstanceof IngestionSession.
OSFQL (#33, #34, #43)
OsfqlResponse.updatedTermIdsis required and disjoint fromproducedTermIds. Count created and updated terms from the two lists directly.osfql.diagnose(query, requestOptions)isosfql.diagnose(query, options?, requestOptions?).OsfqlPreviewAffected.exactis required.- The atomic refusal code
drop_sort_in_atomic_programisdrop_sort_not_alone. ADROP SORTorALTER SORTmust be the only statement of its program, withatomic: falsetoo. RiskTiergainsschema_destructive(DROP SORT).- UI dry-run
affectedPreview:sampleRowsvalues are taggedOsfqlValues keyed by feature plusterm_id;countandstatementcover the whole program;exact,mutatesandstatementsare new required fields.
Terms, rules and sorts (#32, #35, #40, #41, #46)
terms.termExists(id)returns the engine’s answer. It returnedtruefor every id.FeatureInputValueDtogains the tagged{ type, value }member.RuleTermDraftDto.featuresisRecord<string, string | number | boolean>.- Offset iterators: when a page reports
total, a short page no longer ends the walk. TermVersionDto.seededis required; versions are listed newest first.RegisterFunctionRequest.tenantIdandEvaluateFunctionRequest.tenantIdare removed (#35).FunctionDraftDtois an alias ofRegisterFunctionRequest.SortReferenceKindgainsterm_reference.DeleteSortResponse.sortsDeletedis required.deleteSort’s options are the namedDeleteSortOptions. The sort-delete refusal body is coded; read it withsortDeleteRefusalOf(error).
Agents, reviews, conversation (#25, #30)
- Every
ReviewsClientmethod returns a typed camelCase result;failedIdsentries are{ reviewId, reason }. AddPendingReviewRequest.sortSuggestionsisReviewSortSuggestion[]({ sortName, confidence, reason? }). The old shape was refused with 422.ConversationClient.getTurnsnarrowsqueryResultsrows through the OSFQL value mapper.ActionReviewsClient.listPendingand the agent lifecycle methods raiseValidationErrorfor anoriginorstatusoutside their unions.
UI, checks, reasoning (#28, #37, #44)
- Removed:
constraints.getGraph(usevisualization.constraintGraph),fuzzy.fuzzyMergeandfuzzy.fuzzySubsumption(usereasoning.merge/reasoning.subsumption),execution.getTrailandexecution.getChoicePoints(no engine route; readtrailLength/choicePointCountfromexecution.getSession). All five called paths no engine serves. UIDescribeRequest.sessionIdis removed; the engine refuses it with 422.UIGenerateResponse.dataisUIDataPayload;diagnosticsandsuggestionsarestring[];verdictisUIGenerateVerdict.CoherenceAnalyzeResponse.reportis a typed camelCaseCoherenceReport;CoherenceSummaryDto.verdictis a union.ConformanceViolationDto.suggestedFixesisSuggestedFix[]; an unknown action arrives as{ action: 'unrecognized', entry }.CheckFinding.proofTraceisCheckProofStep[] | null.TemporalPlanResponse.diversityScoreis optional: absent without a diversity objective.FuzzyProveResponse.certificateis aDiagnosisCertificatewith the engine’s snake_case keys, kept verbatim because it is re-verified by deserializing it exactly as sent.
New required fields on response types (fixtures and mocks must add them)
SourceSummaryDto.lastSyncAt,SourceDetailResponse.lastSyncAt,IngestFromSourceResponse.warnings,OntologyBridge.BindingSummaryDto.sortName(#29).TranslateRdfResponse/IngestRdfResponserepairedIriCount,repairedIris(andsortPreviewson translate);DocumentStatsDto.relationsIntegrated;EncodeClipResponse.disposition,timingsUs; fourFrameSummaryarrays.ActionReviewResponse.producedTermIds,PendingActionReviewDto.origin, tenIntegratedCycleOutcomeDtofields.SubscriptionDto.maxRetries,backoffMs.MaterializeScenarioResponse.goalCreated.papersPooled/totalPapersPooledon research cycles, sessions, summaries and statistics.SendMessageResponse.timestamp;EntailmentResponse/DisentailmentResponsedegree,antecedentDegree,consequentDegree;MaterializationSummaryDtotemporalConstraintsCreated,webhookActionsCreated,toolRegistrationsCreated,warnings;CycleOutcomeSummaryDto.rulesLearned;SortCompareResponsesortA,sortB,operator;LearnedSimilarityDto.meetIsBottom;SimilaritySearchResponse.exhaustive;CreateModuleResponse.success;ImportModuleResponseimporterModule,success;AgentSubVerdictDto.stepIndices;OversightSessionStatusResponse.userIntent;SoftUnifyResponse.embeddingsUsed.
Engine behaviour that reaches SDK callers
memory.rememberanswers 400 for an out-of-rangeimportanceor a policy-refused write.setActionReviewConfiganswers 400 for a zero timeout. A timed-out review decision answers 409.subscriptions.create/updaterefuse loopback and private targets with 400.- Speech: an unreachable sidecar is 502; 503 is always the permanent, coded
speech_synthesis_not_configured. compliance.listrefuses undeclared query keys with 400 (the SDK sends only declared keys).- A
DELETEof a rule’s conclusion answerswrite_targets_derived_fact, notterm_named_by_rules.
- New clients:
client.cron,client.metrics,client.inbox,client.pairing,client.channels,client.platform(backups, branches, config sections, connector health, diagnostics, identities, onboarding, skills),client.plugins(WASM plugins), andclient.memory(#26, #31). Type namespacesCron,Metrics,Inbox,Pairing,Channels,Platform,Plugins,Memory. - Sources:
uploadFile,listFiles,deleteFile(#18). - Ingestion:
queueMetrics,listDlq,countDlq,abandonDlqEntry,recoverStuck,reprocessFailed(#23);ingestUrl,getWebCapabilities;ingestAudio,getAudioJob; the fullIngestionConfigDto(#21); RDFrepairIris,computeEmbeddings,keepDeclarations(#20); merge-audit evidence and the other stats fields; the V-JEPAencodeClipreceipt and per-frame verdicts. - Cognitive:
getActionReviewConfig,pauseAgent,resumeAgent,stopAgent(#24);actionReviews.expire;reviewQueueRefusalsand nine cycle counters. - Conversation:
sendMessageStream,injection,includePromptTrace,promptTrace,matchedTemplates; root exportsdecodeConversationMessageandUSER_DATA_FIELDS(#25). - OSFQL:
OsfqlRequest.semantics('osf' | 'rdf') (#33);OsfqlError(errorType,refusalwithreferrersandcascadeLimit,maxRows) andDeadlineExceededError(#43);diagnoseoptionstimeoutMs/maxRows;OsfqlResponse.sortDelete/sortReparent;OsfqlPreviewStatement.affectedError/sortDelete. - Terms and rules:
TermResponse.etag;ifMatchonupdateTerm,deleteTerm,deleteRule,replaceRule(#32);rejectedon bulk rule and fact writes (#46);iterateTermsoffsetandonPage(#40). - Sorts:
previewSortDelete, subtree delete (descendants: 'delete'),reparentSort,sortDeleteRefusalOf, featuredescriptionandindex. - Ontology:
checkOwl2,pipelineStatus,qualityReport, competency questions,enrich(#19). UI:listPages(#28),UIGenerateResponse.conversationId/dataSources/intents/trace,UIActionResponse.retractedTermIds. NL query TRIZ fields (#27). Typed coherence report and suggested fixes (#37). - Request fields that unlock response fields:
fuzzyProvesealandkbDataVersion,enrollSpeakervariant,visualization.latticelimit/offset,visualization.hypergraphincludeProvenance/includeRelations. OsfqlError.sortDeleteRefusal: the coded refusal of aDROP SORT.- Compliance:
getRecord,getCausalChain,listScopes,getInclusionProof,listfiltersactor/q;RequestOptions.causaland thex-osfkb-*header helpers (readAuditHeaders,readCausalRefusal,sealPreimage, …). subscriptions.update;admin.getMyTenant;authz.writeTuple/listTuples/deleteTuple/check;errorBodyCode.
sources.register()rewrote user-keyed maps inconfig, including MCPtransport.env/transport.headers, which lost credentials (#17).- A path id holding
/,?,#,%or a space addressed the wrong route (#38). - The sources, bridge, RDF, ingestion, conversation, nlQuery,
ui.generate, action-review, cycle and subscription mappers dropped fields the engine sends (#20, #21, #25, #27, #28, #29). - Async ingestion returned sessions with
undefinedstatus and discarded inline results (#22). reviews.addPendingReviewwith sort suggestions was refused with 422 (spec collision, engine #224).iterateTermsstopped after one page whenpageSizeexceeded the engine’s 10 000-row cap (#40).terms.termExistsreturnedtruefor every id.- Five methods called unserved paths (#44).
- 73 response fields in 27 DTOs that the engine sends were dropped by the normalizers; all are mapped.
scripts/check-normalizer-drops.mjsruns in the unit suite, so a field added to the spec and not mapped fails the tests. - A Gaussian, CyclicGaussian or GaussianProduct
FuzzyNumberreadstdDev: undefined;std_devis mapped in both directions. ui.describecustomizations lost camelCase component props (dataKeybecamedata_key, which the engine’sremove_component/reorderlookups then missed).compliance.appendrewrote the keys ofextra, which the engine hashes into the audit chain.- A 503 that no retry can change (a queueless
sync: falseingestion, speech synthesis not configured) is never retried, even withretryOn503.
Documentation
Section titled “Documentation”updatedTermIdsandproducedTermIdsare disjoint (#34); everyOsfqlAtomicRefusal.code,OsfqlRefusal.code, repair kind and diagnostic kind is listed.terms.listVersionsis newest first (#36);BigIntegerValuestates the engine’s current type-hint rules (#41);ProofDto.ruleHeadTermIdgroups by rule (#42).- The engine’s current status codes and refusals on the changed routes: cron timezone, DLQ abandon, document check, derived conclusions, function registration, sort delete, generate citations, visualization constraints, ontology transpile, TRIZ, action-review deadlines.
Known engine gaps (filed)
Section titled “Known engine gaps (filed)”- reasoninglayer#363 (duplicate
operationId), #364 (undeclared statuses, headers and refusal bodies), #365 (OSFQL sort-statement run errors), #366 (review stores), #367 (audioingestion_config), #368 (duplicate diagnostic), #369 (channel routes outlive their tenant), #370 (branch scoping), #371 (tuple write refusal order), #372 (untyped coherence report), and a new collision on #224.IngestionConfigDto.batchSize/maxSchemaTokensare deprecated no-ops until engine #352 removes them.
1.2.1 to 2.1.2 - 2026-05-06 to 2026-09-22
Section titled “1.2.1 to 2.1.2 - 2026-05-06 to 2026-09-22”These entries were collected under an “Unreleased” heading from 1.2.1 through 2.1.2, and were not split per release.
Migrating to 2.0.0
Section titled “Migrating to 2.0.0”-
ClientConfig.authis required.new ReasoningLayerClient({ baseUrl, tenantId, timeoutMs })was valid in 1.27 and now throws at construction:ClientConfig.auth is required — pass { mode: 'bearer', token } or { mode: 'cookie' }.It is called out here because of the failure mode, not the change: the throw is at RUN TIME, so a clean typecheck of an app says nothing about the scripts, seeds and ad-hoc measurement code that also construct a client. Grep for
new ReasoningLayerClientrather than trusting the build.
osfql.execute()carriesupdatedTermIds. The engine puts an updated term’s id in bothproduced_term_idsandupdated_term_ids, and the mapper read only the first — so a client could not tell an insert from an update, or tell whether anUPDATEpattern matched anything.undefinedmeans the engine does not report it;[]means the program updated nothing. (Superseded in 3.0.0: the engine now keeps the two lists disjoint, andupdatedTermIdsis always present.)forwardChain()sendstimeoutMs. The field was typed and documented (a server-side deadline,504when it passes) and the mapper never emitted it, so a caller setting a deadline silently got the 300 s backstop.backwardChain()beside it always sent it.compliance.summary()carries theriskblock andnOversightDecisions. The mapper named six fields and stopped, dropping fourteen risk counters and the oversight count — the EU AI Act Article 15 and Article 14 evidence.risk.fabricationRatestays nullable:nullis “not measured”, and its denominator is checked responses only, so folding it to0would report a clean rate for a ledger nobody checked.conformance.check()returns the violations the route actually sends. They were mapped throughViolationDto— a different struct sharing no key with the response — so every violation arrived withdescription,relatedFactIdandseverityallundefined: a 200, an array of the right length, and no detail.conformance.repair()was never affected.- The sources mappers carry four fields they dropped:
DiscoverableTypeDto.columnCount,DiscoveredFeatureDto.featureName,DiscoveredFeatureDto.identifierProvenance, andSourceTypeDto.configFields.identifierProvenanceis the one that mattered most — it separates a declared primary key from a sampled guess, and a key that is not unique makes a re-ingest duplicate facts rather than update them. osfql.preview()no longer loses the plan over one sample row. An unrecognised value tag in one of ten ILLUSTRATIVE sample rows used to fail the affected block, the statement, and the whole response, andpreview()threw a bareReasoningLayerErrorcarrying no status. The row is now dropped and counted inaffected.unparsedSampleRows; the count, the risk andmutatessurvive.- A malformed term request raises
ValidationError, notTypeError.TermInputDtoFromFrontToApidiscriminates within, which throws onundefined, so a request missingtermproduced an error from library internals that escaped everyinstanceofbranch over the SDK hierarchy. It now names the field, down to the row and the feature (antecedents[1],term.features.tier).
OntologyBridgeClient.executeTranspiled()—POST /api/v1/ontology/query. It was typed on the generated api and never lifted onto the client. The second half of the transpile pair:transpileemits the native query and stops, this runs it — and for atranspile-mode source, whose bound sorts are live views over the operator’s own database, it is the only route that answers at all.rowCount/sourceIdare camelCased;rowsis one object per record.ConstraintDtogainsRealRange—{ type: 'RealRange', value: { min, max } }, theRealtwin ofIntegerRange. The engine has always defined it; consumers were casting past the published union.- New types:
RiskTotalsDto,ConformanceViolationDto,ConfigFieldDto,ExecuteTranspiledRequest,ExecuteTranspiledResponse.
Documentation
Section titled “Documentation”ReferenceDesignatorsays which spelling works. PasssortName; thesort_namespelling shown for the wire is refused, because a designator sits inside afeaturesmap andfeaturesis carried verbatim. The compiler does not catch the swap —ReferenceValue.valueisstring | ReferenceDesignator, so an object with the wrong key stays assignable — and the engine’s refusal names a byte offset and no feature.OsfqlClient.preview’s remarks no longer contradictOsfqlPreviewAffected.count. A destructive statement matching zero rows answers{count: 0, exact: true}; it is the ABSENCE of the wholeaffectedblock that means the statement has no affected-row set. The two are distinguishable, which is what the block was separated for.OsfqlPreviewAffected.exactnarrowed to what the engine gives.truefor every top-level statement; a nested statement is always an upper bound, even when its enclosing guard names no variable. Do not derive it from the shape of the program.BulkRefusedErroris right aboutdryRunwithpartial. The two together answer200withtermsAdded: 0and every row inerrors— they do not throw, because a dry run that wrote nothing has nothing to refuse. That pair is the only shape that answers both halves of a forecast in one call.
Changed — BREAKING
Section titled “Changed — BREAKING”-
ProofDto.goalTermIdis now optional, and it MEANS something different. It used to name the rule’s instantiated head: one id shared by every solution of a goal, and an idgetTerm()answered 404 for. It now names the CONCLUSION the node proved, which joins toclient.query.findBySort()andclient.terms.getTerm(), and two solutions carry different ids when they prove different conclusions. It is ABSENT when no conclusion was materialised — a goal proved with no preceding forward chain stores nothing. Assigning it to astringno longer compiles, which is the point.The old id has not gone away; it moved to the new
ProofDto.ruleHeadTermId.
ProofDto.ruleHeadTermId— the rule’s instantiated head, present on a rule application when it differs from the conclusion. A grouping key, NOT a fetchable id: the term routes refuse it deliberately.SortsClient.getQuotientOrder()— the Definition IV.9 quotient order over the caller’s own lattice: the equivalence classes with their degrees, and the fuzzy partial order between them. Prefer it overgetEquivalenceClasses(), which computes process-wide and then filters.granularityongetPreorderDegree()— read the degree from either of the paper’s two preorders.similarity_deleted(Definition IV.5) is the default here and is what the graded GLB is computed from;combined(Definition IV.1) treats a similarity edge as a step, so a directly-similar pair answers its similarity degree instead of0. The response always echoes the granularity back. ⚠️getQuotientOrder()takes the same two spellings and defaults tocombined.- New types:
SortPreorderGranularity,GetQuotientOrderOptions,GetQuotientOrderResponse,QuotientClassDto,QuotientOrderEdgeDto.
-
nafProve()/proveWithNegation()accept apsi()term. Every other term-carrying method did; these two demanded a hand-built wireTermInputDto, so the documented way to build a term did not work there. -
The
preorder-degreedocumentation wrote Definition IV.5 as((similarity - subsumption) union subsumption)^+, reading.-as an arithmetic difference. It is pair DELETION, and the operands were wrong too. -
FlowNetworksClient (
client.flowNetworks) — generic capacitated directed-graph resource. Persist a graph once, thencreate/get/clone/commit/solveit under any of five algorithms:max_flow,min_cost_max_flow,classify_edges,classify_edges_optimal,min_cut. Mirrors the lifecycle of the/spacesresource (create, clone, commit, solve) and exposes the same Dulmage–Mendelsohn edge trichotomy (always_used/never_used/sometimes_used) the scheduling engine returns over(agent, day, shift)cells, generalised to arbitrary edges. -
New types:
FlowAlgorithm,EdgeSpec,EdgeFlow,EdgeClass(ification),MinCutReport,EdgeCapacityUpdate,CreateFlowNetworkRequest,FlowNetworkResponse,CommitFlowNetworkRequest,SolveFlowNetworkRequest,SolveFlowNetworkResponse(exported under theFlowNetworksnamespace). -
New guide: Flow Networks.
Removed
Section titled “Removed”-
BackwardChainRequest.expandBindingsandBackwardChainResponse.referencedTerms(theRecord<string, TermDto>sidecar map). These existed only to support a prior backend feature where scheduling was solved as a backward-chain query returning deeply-nested Ψ-term lists; the backend has dropped that path in favour of the dedicated scheduling and flow-network endpoints, and the SDK fields are no longer wired to anything.Note:
TermDto.referencedTerms(a different field —Record<string, ReferencedTermSummary>on the term-retrieval surface) is unaffected.
[0.1.0] - Unreleased
Section titled “[0.1.0] - Unreleased”Initial release of the @kortexya/reasoninglayer TypeScript SDK.
Core infrastructure
Section titled “Core infrastructure”ReasoningLayerClientmain client with resource-as-property access patternClientConfigwithbaseUrl,tenantId, and optional auth headers- HTTP layer with automatic retry (429, 503), exponential backoff with jitter, and configurable timeouts
- Error hierarchy:
ReasoningLayerError,ApiError,ConstraintViolationError,RateLimitError,TimeoutError,ValidationError,NetworkError — this never shipped. The entry was written from the design and no resource client grew the accessor. Use an interceptor for a successful response’s status and headers, and.withMetadata()on all resource clients for access to HTTP status, headers, and rate limit infoApiError’s ownstatus/headerson a failure.- WebSocket client with auto-reconnect for real-time event subscriptions
Type system (types/)
Section titled “Type system (types/)”ValueDtotagged discriminated union (10 variants: String, Integer, Real, Boolean, Uninstantiated, Reference, List, FuzzyScalar, FuzzyNumber, Set)FeatureValueDtountagged union for homoiconic inference endpointsFuzzyShapeDtowith"kind"discriminator (Triangular, Trapezoidal, Gaussian, CyclicGaussian)TermInputDtostructural union (Reference, Inline by sort ID, Inline by sort name)FeatureInputValueDtostructural union (10 variants including ConstrainedVariable and Variable)- Full DTO types for all domains: sorts, terms, inference, query, fuzzy, cognitive, constraints, namespaces, collections, execution, causal, ingestion, reviews, visualization, ILP, reasoning, statistical, control, spaces, row, sources, communities, utilities, scenarios, action-reviews, discovery, extract, scheduling
Builders (builders/)
Section titled “Builders (builders/)”Valuenamespace: tagged value construction (Value.string(),Value.integer(),Value.fuzzyNumber(), etc.)FuzzyShapenamespace: membership function shapes (FuzzyShape.triangular(),FuzzyShape.gaussian(), etc.)FeatureInputnamespace: untagged value construction for inference (FeatureInput.string(),FeatureInput.variable(),FeatureInput.constrainedVar(), etc.)TermInputnamespace: term input construction (TermInput.byName(),TermInput.byId(),TermInput.ref())guard()function: shorthand for guard constraints with 6 operators (lt, lte, gt, gte, eq, ne)allen()function: Allen temporal interval constraints with 13 relationsSortBuilderfluent API: chained sort definition with parents, features, bound constraints, and descriptionspsi()shorthand for inline Psi-term construction with auto-coercion
Resource clients (resources/)
Section titled “Resource clients (resources/)”- SortsClient: createSort, getSort, deleteSort, listSorts, bulkCreateSorts, isSubtype, computeGlb, computeLub, decodeGlb, compareSorts, getChildren, getParents, getAncestors, getDescendants, getCompatible, indexSorts, updateReviewStatus
- TermsClient: createTerm, getTerm, updateTerm, deleteTerm, termExists, bulkCreateTerms
- InferenceClient: addRule, addFact, bulkAddRules, bulkAddFacts, getFacts, clearFacts, backwardChain, forwardChain, forwardChainTagged, fuzzyProve, bayesianPredict, nafProve, createGoal, getGoals, getGoal, deleteGoal, getMetaSorts
- QueryClient: findUnifiable, findBySort, osfSearch, validateTerm, validatedUnify, nlQuery
- FuzzyClient: fuzzyUnify, fuzzyMerge, fuzzySubsumption, searchTopK, predictEffect
- CognitiveClient: createAgent, getAgent, deleteAgent, getState, runCycle, addBelief, addGoal, addRule, addSort, recallEpisodes, getEpisodeStats, addHtnMethod, sendMessage, broadcastMessage, markMessagesRead, provideFeedback, subscribeToEvents (WebSocket)
- ConstraintsClient: constraint sessions, one-shot solving, graph visualization
- NamespacesClient: namespace hierarchy, imports, metadata
- CollectionsClient: collection CRUD, tagging
- ExecutionClient: Execution sessions, backtracking, goal stack
- CausalClient: Pearl’s causal hierarchy, interventions, counterfactuals, root cause analysis
- IngestionClient: document ingestion, markdown, RDF, session management
- ReviewsClient: human-in-the-loop review operations (approve, reject, merge, re-extract)
- VisualizationClient: lattice, hypergraph, GLB/LUB trace, residuation state, trigger dependency
- IlpClient: inductive logic programming (pattern learning, evaluation, GFlowNet, synthesis)
- ReasoningClient: entailment, evidence assessment, residuation, resource coordination, temporal planning
- StatisticalClient: correlation, partial correlation, conditional independence, causal discovery
- ControlClient: Prolog-style proof control (cut, findall, forall, NAF, conditionals, global state)
- SpacesClient: computation spaces (create, clone, commit, search)
- RowClient: row polymorphism (schema-flexible search, unification, integration, dynamic queries)
- SourcesClient: data source registration, schema discovery, ingestion
- CommunitiesClient: community detection, memberships, search
- UtilitiesClient: string operations, relational arithmetic, term copying
- ScenariosClient: scenario CRUD and verification
- ActionReviewsClient: autonomous action approval (approve, reject, modify, bulk operations)
- DiscoveryClient: causal effect discovery and prediction
- ExtractClient: entity extraction from text
- SchedulingClient: capacitated bipartite b-matching for staff rostering and assignment problems —
feasibility()returns the per-cell trichotomy (confirmed_true/confirmed_false/free) under hard rules;optimize()adds soft preferences and returns the trichotomy across every optimal rota
SDK diagnostics
Section titled “SDK diagnostics”SDK_VERSIONexported constant (0.1.0)X-SDK-Versionheader sent on every HTTP request for backend diagnosticsscripts/generate-route-manifest.ts— generates endpoint manifest from OpenAPI specscripts/check-completeness.ts— reports SDK endpoint coverage vs. OpenAPI spec
Build and tooling
Section titled “Build and tooling”- Dual ESM/CJS output via tsup
- TypeScript strict mode with zero
anytypes - Zero runtime dependencies
- ES2022 target (Node 18+, modern browsers, Deno, Bun)
- Vitest test framework
- pnpm package manager with strict resolution