> ## Documentation Index
> Fetch the complete documentation index at: https://developer.kodexa.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Change Log

> Release notes and platform updates for the Kodexa AI Platform, including new features, breaking changes, fixes, and migration guidance for each release.

<Update label="2026-08-27" tags={["Release", "2026.10", "Platform", "API", "SDK", "Service Bridges", "Document", "Data Forms", "Tasks", "Orchestrator", "CLI", "Analytics", "Chat", "Activities", "Manage", "Breaking"]}>
  ### Release 2026.10

  Rollup of every customer-facing change in the 2026.10 GA release. The headline items: the OpenAPI specification now describes every request and response precisely — separate create and update schemas, required and nullable fields, named enums, unique operation IDs — with the Python SDK regenerated to match; activity-plan agents and module executions can call the service bridges your organization has configured, and every bridge call is now fenced to public hosts; and the document engine closes a long list of gaps in validation exceptions, formula recalculation, selectors, and multi-view editing. `kdx sync push` now converges deletions inside the regions your files own, and module runtimes deliver finished work through brief orchestrator unavailability. Four changes are breaking — the OpenAPI contract, the regenerated Python SDK, the service-bridge egress fence, and `documentStatus` on document families — see the upgrade notes under API, SDK, and Service Bridges.

  **API:**

  * **The OpenAPI specification now describes every body precisely — separate create and update request schemas, required and nullable fields, named enums, and unique operation IDs (breaking).** The specification served at `GET /v3/api-docs` (and everything generated from it) now yields three schemas per resource: `<Entity>` for responses, with `required` listing the fields the server always returns and `nullable` marking those that can be `null`; `<Entity>CreateRequest` for `POST` bodies, with server-generated fields (`id`, `uuid`, `createdOn`, `updatedOn`, `changeSequence`) removed and `required` naming only what the server insists on (for example `TaskCreateRequest` requires just `projectId`); and `<Entity>UpdateRequest` for `PUT` bodies, which requires nothing, keeps `changeSequence` for optimistic locking, and omits the ownership fields an update never writes. Enumerations are emitted as named components referenced by `$ref` — `TaskStatusType`, `ExecutionStatus`, `ExecutionStatusMessageType`, `SortDirection`, `AuditAction`, `ChatMessageRole`, `WebLlmModelSize`, and others — so every generator produces one stable type per enum instead of inventing names like `Type1` or `StatusType3`. Nullable object references are expressed as `allOf` + `nullable: true`, so generated types carry both facts. Fields that hold free-form JSON (for example the storage on `Workspace`, prompt metadata, and platform-event payloads) are typed as JSON objects rather than base64 strings. Operation IDs are unique: `PUT /api/tasks/{id}/status` is `setTaskStatus` and `GET /api/task-groups/{id}/history` is `listHistoryForTaskGroup`, which lets `updateTaskStatus` (`PUT /api/task-statuses/{id}`) and `listTaskGroupHistory` (`GET /api/task-group-history`) appear in generated clients — both were previously dropped by the name collision; `cancelExecution` is defined once, at `/api/executions/{executionId}/cancel`. The retired project-scoped task status type (`TODO`/`IN_PROGRESS`/`DONE`) is gone: `TaskStatusType` is `OPEN | IN_PROGRESS | DONE | BLOCKED | PENDING`, and project templates that still declare a task status with `statusType: TODO` are accepted and stored as `OPEN`. Request and response JSON on the wire is unchanged; only the specification and the clients generated from it change.

    **Upgrade note.** If you generate a client from `/v3/api-docs`, regenerate it against a 2026.10 server and expect compile-time changes: create calls take `<Entity>CreateRequest` and update calls take `<Entity>UpdateRequest` instead of the response type; response fields that were all optional are now typed as required where the server always sends them; enum types take their component names; JSON-blob fields become objects instead of strings; and two operations are renamed — `updateTaskStatus` → `setTaskStatus` for `PUT /api/tasks/{id}/status` and `listTaskGroupHistory` → `listHistoryForTaskGroup` for `GET /api/task-groups/{id}/history` (the old names now refer to the task-status and task-group-history entity endpoints). Hand-written HTTP integrations need no change.

  * **`documentStatus` on document families is now a full status object (breaking).** Document family reads — `GET /api/document-families`, `GET /api/document-families/{id}`, and the responses of the status `PUT`/`DELETE` endpoints — return `documentStatus` as the `DocumentStatus` object (`id`, `status`, and its other fields) instead of always `null`, and saved filters such as `documentStatus.status:'Reviewed'` resolve against it. In the Studio document grids the Status column now shows each document's status and redraws as soon as you pick a new one. The generic `PUT /api/document-families/{id}` also accepts `documentStatus` as an object under the standard update permission.

    **Upgrade note.** In the OpenAPI schema, `documentStatus` on `DocumentFamily` and on its create/update request bodies changes from `type: string` to a reference to the `DocumentStatus` schema. Regenerate typed API clients and update any code that declared the field as a string; read the status through `documentStatus.status` (or `documentStatus.id`) rather than treating the field itself as the value.

  * **Filter task groups by their member tasks.** `GET /api/task-groups` now accepts a `memberTask` relationship in `filter`, so you can select groups by properties of the tasks they contain — for example `memberTask.statusSlug in ['reviewed']`, or `not (memberTask.updatedOn > '2026-08-01T00:00:00Z')` to find groups with no recent task activity. A group matches when at least one of its non-deleted tasks satisfies the condition. This is additive and opt-in; existing filters and responses are unchanged.

  * **`POST /api/batch-update` reports the real outcome of every write.** A batch save completes when only optional bookkeeping fails, and when a write that matters fails — the task update, task-group maintenance, labels, document-family deletes, or the status auto-lock — the response carries that write's own error and reason rather than a generic commit failure. A save that reports success has been stored.

  * **`taskId` removed from the Activity schema.** The `Activity`, `ActivityCreateRequest`, and `ActivityUpdateRequest` schemas no longer include `taskId`; the field was never populated by the server, so no data is lost, and the generated Python SDK and TypeScript models drop it accordingly. The link between a task and the activity that created it is the task's `createdByActivityId`.

  **SDK:**

  * **Python SDK models regenerated against the new contract — request models, spec-named enums, and a slimmer `Task` (breaking).** `kodexa_document.model._generated` now includes a `<Entity>CreateRequest` and `<Entity>UpdateRequest` model for every resource (154 new models) alongside the response models, so create and update payloads can be built from a typed model that contains only the fields the server accepts. Enumeration classes carry the names from the API specification instead of generator-invented ones: `Status` → `ExecutionStatus` (also the top-level export, `from kodexa_document.model import ExecutionStatus`), `StatusType4` → `TaskStatusType`, `Type6` → `TaskActivityType`, `Direction` → `SortDirection`, `Action` → `AuditAction`, `ActorType` → `DocumentActorType`, `Cardinality` → `TaxonCardinality`, `ValuePath` → `TaxonValuePath`, `MetadataValue` → `TaxonMetadataValue`, `AnalyticsDatasetFieldType` → `AnalyticsFieldType`, `ModelType` → `WebLlmModelSize`, `Outcome` → `TaskSignalOutcome`, `State` → `SessionState`, `StepType` → `PipelineStepType`; the retired `StatusType3` (`TODO`/`IN_PROGRESS`/`DONE`) and the duplicate `TaskstatusTaskStatus` model are removed. `Task` now carries `status_slug` only — resolve the label, color, and `status_type` through the organization's task statuses (`TaskStatus`) rather than an embedded object.

    **Upgrade note.** Update imports that reference the old enum names listed above (`kodexa_document.model.Status` is now `ExecutionStatus`). Code that read `task.status` or `task.status_id` must switch to `task.status_slug` and look the status up in the organization's task statuses; `TaskEndpoint.create_with_request()` ignores an embedded status object and sends `statusSlug`. Values on the wire are unchanged — only the Python type names and the `Task` model's fields differ.

  * **Python SDK attribute writes are complete and type-safe.** Setting a date attribute from the Python SDK or an agent tool now persists — previously the value was dropped while the call reported success — and date strings are parsed flexibly: `2023-06-30`, month-name and US numeric forms as well as RFC 3339. Updating a boolean or date attribute no longer raises `DocumentError` after the value was written, and only the field that actually changed triggers recalculation. `Document.batch_transaction()` now persists `value`, `tagId`, `dataFeatures`, confidence and timestamps on attributes it creates, keeps `selectionOptions` on data objects it creates, and recalculates formulas for attributes it creates or updates — the same behavior as the direct accessors. Closing a document also releases its formula, validation, and conditional-format caches, so long-running processes that open many documents no longer grow memory per document.

  **Service Bridges:**

  * **Activity-plan `AGENT` steps and module executions can call service bridges.** An agent running as an `AGENT` step gets two tools, `list_service_bridges` and `call_service_bridge` (`bridge` and `endpoint_name` required, optional `query` and `body`; returns `{status, ok, body, truncated}`), so any external HTTP API your organization has configured as a service bridge — a search provider, an enrichment service, an internal system — becomes something the agent can use, with the credential injected server-side and never entering the agent's container. Access is opt-in and fails closed: a bridge is callable by agents only when its new `agentCallable` flag is on (the **Callable by agents** toggle on the bridge's General tab, or `agentCallable: true` in the bridge YAML) **and** the bridge is bound to the agent's project; agents see only bridges that satisfy both. The same two checks admit module and task executions running as a project's assistant, so a module can call the bridges bound to its own project through the bridge proxy; any call that cannot be resolved to a project is denied. Agents also receive document tools for the document families they were dispatched for, and any document an agent creates during the activity appears on the review task the activity ends in.

  * **Activity plans containing `AGENT` steps deploy through `kdx sync push`.** `AGENT` is accepted by server-side plan validation, so a plan carrying an agent step syncs like any other plan instead of being rejected as an unknown step type.

  * **Service bridge calls are fenced to public hosts and refuse credential configuration that cannot work (breaking).** Service bridge calls made through the bridge proxy — from data forms, `BRIDGE_CALL` steps and agents — go through the platform's egress guard: targets that resolve to loopback, private, link-local, or cloud-metadata addresses are refused, and each redirect hop is re-checked rather than followed blindly. A refused call from a data form or the bridge proxy returns `400` with `service bridge egress denied` and the reason; a refused `BRIDGE_CALL` step fails with the same reason. A header configured with `secretRef` is now rejected with a `400` naming the header instead of being sent upstream with an empty value; `${secrets.NAME}` interpolation in the header `value` on `defaultHeaders` is the supported way to inject a secret. A call naming an endpoint that does not exist on a bridge now fails with an error listing the bridge's available endpoint names, instead of a bare "not found" — and a script or step that names a wrong endpoint no longer falls through to the bridge's first endpoint. An omitted endpoint name still selects the sole endpoint of a single-endpoint bridge.

    **Upgrade note.** Audit bridges whose `baseUrl` points at a private or in-cluster host — those calls now fail with `service bridge egress denied`. Self-hosted deployments that must reach an internal mock or test service can set `BRIDGE_EGRESS_PRIVATE_HOST_ALLOWLIST` (a comma-separated list of exact hostnames) in the environment of both the API and the orchestrator; matching is exact and case-insensitive with no wildcards or CIDR ranges, DNS pinning and redirect checks still apply, and an active allowlist is logged as a warning at startup — it is intended for development and test environments only. Move any `headers[].secretRef` to `value: "${secrets.NAME}"` under `defaultHeaders`. Confirm that scripts and forms reference an endpoint `name` that exists on the bridge — a misspelled name is now an error rather than a call to the first endpoint.

  * **Service bridge required-field checks now recognize parameters sent in the query string.** When an endpoint declares `required` fields in its `requestSchema`, the proxy (`/api/service-bridges/{id}/proxy/{endpointName}`) returns an empty `[]` result without calling upstream while any of them is missing — the behaviour that keeps a dependent dropdown showing no options until its prerequisite field is filled. That check now treats a required field as supplied when it appears in either the JSON body or the query string, so GET-style endpoints that carry their parameters in the URL — the usual shape for search and lookup APIs — reach the upstream service instead of quietly returning an empty result. Presence is what counts on both sides: an empty string, `0`, or `false` still satisfies the field.

  * **Edits in the service bridge editor's YAML tab now save.** Changes typed in the YAML tab reach the bridge on save — including when you switch tabs or close the editor mid-edit — and the toolbar spells out that editing here replaces the whole bridge definition, including fields on the other tabs. YAML that does not parse, or parses to something other than a bridge object, is held back with an explanation instead of committing. If the bridge changes underneath you while you have uncommitted YAML (for example, an agent updates a draft), your text is kept and a banner tells you it is now based on the older version.

  **Document:**

  * **Validation rules can read parent values with `{../…}`.** A validation rule or condition that references a parent or grandparent attribute — `{../invoice_number}`, `{../../customer_name}` — is evaluated during extraction, during recalculation after an edit, and when a data definition is saved. A reference that keeps walking up past the top of the data — a second `..` applied to an object that has no parent — is reported as an evaluation error rather than resolving to nothing. Documents whose data carries a circular parent link open and save normally.
  * **On Studio-built data definitions, a corrected value closes its validation exception on the first correction.** Where an element's internal name differs from its external name — the shape Studio produces — correcting a value that raised a validation exception closes that exception on the first correction, whichever evaluation path opened it.
  * **Exception flags from validation rules stay open until the rule passes.** A validation rule authored with an empty `exceptionId` is identified by its taxon path and rule name, so every rule on a taxon carries its own flag: a flagged document stays flagged across data-definition saves and document refreshes until the underlying value is fixed. Deleting a flagged field in the review form removes that field's exception along with it. Documents carrying flags from such rules are corrected on their next refresh — no reprocessing needed.
  * **Overridable validation rules can be overridden in the review workspace, and exception details persist through every write.** A validation rule with `overridable: true` in the data definition now produces exceptions that carry the Override action in the browser, on documents already processed — no reprocessing or redeploy needed — and a rule's `supportArticleId` is carried onto its exceptions the same way. Overriding an exception keeps it on screen in a muted "Overridden by user" state, and the override holds across later validation passes while the rule still fails; only exceptions the platform itself closed are reopened when their rule fails again. Exceptions written by extraction, recalculation, or the browser now retain their `overridable` flag, linked support article, and evaluation-errored status, and previously processed documents pick these values up silently the first time they are opened. Separately, editing or moving a data object or attribute now preserves its original creation timestamp instead of blanking it, so creation times read from exported document databases are reliable.
  * **Data exceptions keep their configuration-error state through re-validation and save.** When a validation rule has a configuration error, its open exceptions carry that state — the error banner, whether the exception can be overridden, its details, and its support-article link. Re-saving the data definition now refreshes that state on the existing exceptions (a rule whose error is fixed clears the banner and details), and the state is now part of the document's saved change history, so it survives reload and shows identically for every user rather than only in the session that produced it. Existing documents pick up the new fields automatically; no action required.
  * **Removing a validation rule now takes effect when a document is reloaded.** Reloading a document applies the current data definition in full: added and changed rules are evaluated on load, and open exceptions raised by a rule that has since been removed from its taxon are now closed on reload, with the close recorded in the document's change history — no reprocessing needed. Removing an entire taxonomy, or removing every rule from a taxon, still requires reprocessing to clear those exceptions.
  * **Saving a data definition only closes the validation-rule flags it owns.** When a data definition is created, updated, or deleted, the engine re-evaluates validation-rule exceptions and closes only the rule-based flags that no longer apply. Data-type conversion errors, formula and content errors, extraction-time flags, and selection-validation flags stay open until their own source clears them or a user closes them — a data-type conversion error clears when the value is re-extracted or a reviewer closes it, not on a definition save.
  * **Deleting a data object removes its validation exceptions with it.** When you delete a row, the exceptions raised on it are removed in the same change, so they no longer linger on the document or appear in the save as flags pointing at an object that no longer exists.
  * **Edits made in a popped-out document window are saved.** When a document is open in a popped-out sidecar alongside the main workspace, adding, editing, moving, copying, or deleting data objects, values, and notes from the popped-out window is written to the document that saves, so the change survives reload and appears in the change history, and the main workspace catches up within a moment. A write the platform refuses is reported once with a clear notification (for example "The value could not be deleted — it has not been removed") rather than appearing to succeed, and a partially applied multi-row move or copy says which part applied. Deleting a row succeeds even when re-validation raised exceptions on it in the same change, deleting a value by its identifier reaches the stored document on every path, and tag highlights load reliably when a document opens.
  * **A save containing changes that can never apply keeps everything else.** When some operations in a save reference a parent or object the document no longer has, the remaining operations are applied and the delta is recorded with the new `APPLIED_PARTIAL` state on `GET /api/linked-deltas`, with `errorMessage` summarizing what was dropped. A save in which nothing could apply is still marked `FAILED`.
  * **A failed server-side save leaves the document exactly as it was.** When a processing step's write to a document is abandoned partway — an error during a large rewrite, for example — the document reverts cleanly to its previous contents and passes integrity checks, with no reprocessing needed. Recovery from a hard crash in the middle of a write is unchanged: reprocess the document.
  * **Highlight adjustments save with their content links intact.** Moving or resizing a tag's highlight during a review removes and re-adds the link between the tag and its text; the saved change set now records the final state of each tag-to-content link, so a tag re-linked to the same text keeps its highlight after save and a removed link stays removed — including when another tag was removed in the same session. Tags removed in a session no longer leave dangling references in the saved document.
  * **Save enables for label removals and task status or priority changes.** Clearing selected labels on a document, and changing Status or Priority in the Task Metadata panel, mark the document as having unsaved changes, so Save is available and the change is included in the next save.
  * **Formula values corrected when a document opens are saved back to the document.** Opening a document whose formula fields are stale — inputs changed since the value was last calculated, or a value stored under the wrong type marker — recalculates them for display and writes those corrections to the stored document in the background, so exports, downstream steps, and other viewers see the same values without anyone pressing save. Corrections are written only from the browser tab that owns the document. A locked task or a locked document family is corrected too — only the calculated values are written and the task stays locked — so a signed-off task's export matches what reviewers see. Corrections are recorded as automatic corrections rather than reviewer edits, and anything you type while the document is opening is saved as your own edit. If the background save does not go through you see a notification, and the corrections are recomputed the next time the document opens.
  * **Recalculate All, data-definition saves, and document refreshes recompute formulas on Studio-built data definitions.** The batch formula passes — Recalculate All, saving a data definition, and document refresh — now recompute every formula field, including on data definitions created in Studio, where an element's internal name differs from its external name. Expect the first definition save on a document that holds stale formula values to update them.
  * **Bulk move and copy of data objects are faster, and bulk copy is all-or-nothing.** Copying many data objects at once from a grid's bulk actions either lands every object or none of them, and a failure is reported instead of leaving a partial result that reported success. Large bulk moves and copies complete in a fraction of the time they used to — a 48-object copy finishes in well under a second — and the grid shows the moved or copied objects as soon as the engine finishes instead of re-downloading the whole document. A second bulk action started right after one completes now runs instead of being ignored.
  * **Selector edge cases evaluate as written.** A positional predicate selects the nth match (`//line[0]` is the first — indexes are 0-based); every condition in an `and` is applied; a comparison against `false` such as `[hasTag('x') = false]` matches nodes that do not carry the tag; and `hasFeatureValue(type, name, value)` compares against the feature's stored value. Predicates apply in the order written, after the node-type test. Because `hasFeatureValue` matches, documents processed with the AWS Textract model (`kodexa/aws-textract-model-v3`) outside lightweight mode now gain the `FormAssistant/Label` and `FormAssistant/Value` form-label tags. Re-check any selector using these shapes that you had worked around.
  * **Dates written with month names, dashes, or a UTC offset convert to the value you meant.** `2026-Mar-01` and `1-Jan-2025` convert to 1 March 2026 and 1 January 2025, and RFC 1123 timestamps convert correctly on every weekday. A space-separated date-time carrying a numeric UTC offset (`2026-03-01 12:00:00 -0500`) is stored at the instant the offset states — 12:00 at `-05:00` is 17:00 UTC. Purely numeric dash dates (`04-30-25`, `1-2-2026`, `03-1-26`) are unchanged. Values written with an offset before this release may be stored at a different instant, or a different date, than they are now; re-extract them if you compare against them.
  * **`addFeature` reports a value it cannot store, and leaves the attribute unchanged.** In event-subscription scripts and script steps, `addFeature` on a data attribute normalizes its value at the script boundary: a cyclic or otherwise non-serializable value raises a catchable script error and the attribute is left untouched. Typed arrays are stored as base64 and integer values as floats immediately — the same shape a reload produces.
  * **Explain Plan phases close with a status and duration, and the detail pane opens at full width.** Every top-level phase in a document's Explain Plan — the extraction run, knowledge application, and preprocessing — is persisted with a completion status and a measured duration, so the plan and processing-step analytics show how long each phase took. A phase that recorded its own outcome, such as an error or a skip, keeps it, as does a duration the phase measured for itself. Clicking a node opens the detail pane at its configured width, flush to the edge of the panel.
  * **PDF Parser: more accurate scan detection, with new opt-in tuning options.** The `PDF Parser` module (`fast-pdf-model`) now handles three cases that could misroute a healthy text PDF: metadata containing stray control characters, a `pdftotext` warning exit that still produced usable output, and a `min_scan_pages` of `0` or a non-numeric value (which falls back to `1` with a warning). All three are extracted normally rather than routed to OCR. The advanced per-page detector gains four options, all off by default: `text_margin_ratio` (ignore text within this fraction of the page edge — scanning stamps, Bates numbers, burned-on headers — when judging whether a page has a usable text layer; also applies to the standard detector), `use_summed_image_coverage` (measure the union of all images on a page, catching scans tiled into strips), `check_font_encoding` (treat text that extracts as mojibake as a scan), and `advanced_full_page_scan` (examine every page rather than stopping once `min_scan_pages` pages are confirmed, so the recorded page list is complete). A new `scan_detection_shadow` option records per-page scan results on the document and its page nodes without changing the verdict or routing, and when the General Parser hands a document to OCR, the scan-detection reason is preserved on the OCR output under a `fast_pdf_scan_detection` metadata entry.
  * **Advanced scan detection labels only the pages it examined.** With `use_advanced_scan_detection` or `scan_detection_shadow` enabled on the PDF Parser module, page nodes carry `fast_pdf:is_scan` (true/false) and, on scanned pages, a `fast_pdf:scanned` presence marker — written for the pages the detector examined and no others. A page the parser could not read carries a `fast_pdf:scan_error` marker instead of an `is_scan` verdict, and pages the walk never reached carry no label at all, so an absent feature means "not measured", never "not a scan". Select scanned pages with `//page[hasFeature('fast_pdf','scanned')]`. Document metadata describes the walk itself: `scan_pages_examined` is how many pages were examined, `scan_detection_mode` is `enforce` or `shadow`, and `scan_detection_complete` says whether the walk covered the whole document — it is `false` whenever the walk stopped short, which includes the ordinary case of stopping as soon as `min_scan_pages` scanned pages were confirmed, so read it as "this page list is partial" rather than as a failure flag. Pages already confirmed as scans always count toward `min_scan_pages`. For a complete page list, use shadow mode or `advanced_full_page_scan`. Both detection options remain off by default.
  * **Document Preprocessor: pages are rasterized only when they really contain vector artwork.** The preprocessor's vector-graphics check now matches PDF drawing operators as whole tokens, so ordinary page text no longer counts as drawing (for example the letters `re` inside a word, or `m` and `c` inside a `cm` transform). In testing the share of pages flagged as vector graphics fell from about 94% to 74%, so far fewer text-only pages are rasterized at 300 DPI — faster preprocessing, with unchanged output for pages that truly carry vector graphics. Clip-only paths (`W`/`W*`) are still excluded.

  **Data Forms:**

  * **A cleared field is treated as empty everywhere in the review form.** When a reviewer clears a value, the form treats that field as empty from then on: re-extracting refills the cleared field instead of adding a duplicate, clearing the last value in a grid row lets the now-empty row be removed rather than left behind, copy actions and formula explanations read the current value, and data-form script triggers fire with the cleared (empty) value rather than the original extracted text. No action required.
  * **Clearing a date, date-time, currency or number value now clears it — on screen, in the saved data, and after reload.** Using a form's clear-value shortcut or emptying the field blanks the stored value and its displayed text together, the field renders empty immediately, and it stays empty when the document is saved and reopened. Clicking into and out of an untouched date field no longer registers as an edit, so it does not dirty the document or block a clear that follows. Form-declared data-entry shortcuts keep acting on the field you were just in, so a modifier that blurs the field on the way into a key chord (an Alt-based combination, for example) no longer stops the shortcut from working.
  * **Edits to numeric, date, and boolean fields persist exactly as entered.** Typing a new value into a typed field — number, currency, percentage, decimal, integer, date, date-time, or boolean — updates that field's value and leaves the originally extracted text in place as provenance, so what you entered is what the saved document holds after reload; clearing such a field still clears it outright. Re-tagging a field from a new selection in the document records the newly selected text as that field's value and provenance; on a numeric or date field the typed value is not re-derived from the new text, so enter it directly if it should change too. No action required.
  * **`setAttribute` in the form script bridge stores values by the attribute's type.** `kodexa.data.setAttribute(dataObjectUuid, path, value)` on an existing attribute now writes into the column that matches the field's type: a numeric string (grouping commas allowed) or a JS number becomes the numeric value, a date becomes the date value — a date-only literal such as `"2026-01-01"` is accepted and stored as midnight — and booleans set the boolean value. The write goes through the same audited update path as a reviewer's edit, so it is saved, survives reload, and appears in the change history; the attribute's original extracted text is left unchanged. If your scripts pre-formatted values to work around dates or numbers not saving, that is no longer necessary.
  * **Copy rules keep the original extracted value on copied data, and multi-select copies follow the rule.** A copy made through a copy rule with attribute mappings now carries the source attribute's original extracted value and confidence onto the copy, and the copy records its source as provenance — so a correction a reviewer made before copying remains detectable on the copy, and object history shows the object as a copy. Selecting several data objects and copying them under a copy rule now produces exactly the result of copying them one at a time: attribute mappings, `copyAttributes: false`, and `stampAttributes` are applied the same way regardless of how many rows are selected.
  * **Audit/Notes opens on the value you picked, from any workspace layout.** Choosing **Audit/Notes** on a data value opens the Audit panel with that value's thread already showing, including when the side panel was collapsed. Closing the side panel with the toggle and reopening it returns you to the tab you were last using rather than to Chats. In layouts where no audit view is available for the current task, the Audit/Notes menu item is hidden instead of being shown but inactive.
  * **The Formula Execution Trace shows the correct sign on every component.** When you open the execution trace for a formula value, subtracted operands are shown as subtractions and the tree view picks the correct top-level operator in mixed expressions such as `(A + B) - C`, so the trace reads the way the formula is written rather than listing every component as an addition.

  **Tasks:**

  * **A task's documents now open in a deterministic order, with the first document as the primary.** Task document links and activity document links carry a new integer `ordinal` field (`TaskDocumentFamily`, `ActivityDocumentFamily` and their create/update requests in the API, Python SDK and CLI models). The ordinal is stamped from each document's position in the `documentFamilyIds` list the activity was started with, copied onto the task's documents when the task is created, and used by the workspace to order a multi-document task — so the task opens on the first document you submitted rather than an arbitrary one, and you choose the primary document by listing it first. Ordinal `0` is the primary document. A `POST /api/task-document-families` or `POST /api/activity-document-families` that omits `ordinal` (or sends `0`) appends the document after the existing ones; an explicit non-zero ordinal is stored as given. Existing multi-document tasks and activities were backfilled with a stable order.
  * **Task completion is guarded when a document's latest changes are not yet in the stored document.** Marking a task done — from the status dropdown, `PUT /api/tasks/{id}`, `PUT /api/tasks/{id}/status`, or a batch update — returns `409 Conflict` naming the affected documents when one of the task's documents still has unapplied changes from its most recent save. Reopen the document, redo those changes, and save; a successful save clears the block and the task completes normally. Only the latest save per document is checked, so an older failure that has since been re-saved does not block. Error dialogs now show the server's own explanation for any `4xx` response, so the reason is visible immediately instead of a generic "Request failed" line.
  * **Task actions' `attributes` block now persists writes to existing values.** When an action's `attributes` block stamps a field that already has a value — for example `accepted_by` / `accepted_at` on Accept, or clearing them on Re-open — the write now goes through the same audited update path as a reviewer edit, so it is saved with the document, survives reload, and appears in the change history. The stamped value is stored according to the target field's type (number, date, boolean, or text), and the attribute's original extracted text is left unchanged. Creating a new attribute via `attributes` behaves as before.
  * **Task status names and colors display consistently wherever a task appears.** The home dashboard, related-task popovers, kanban cards, the workflow task navigator, the parent-task chip, and sub-task timelines now resolve a task's status through the organization's task statuses, so they show the configured status name and color (for example "In Review") rather than the raw slug or no status at all.
  * **Task Metadata status keeps its value after a batch save.** The task object returned by `POST /api/batch-update` now carries the status under `statusSlug`, matching the Task model everywhere else, so the Status dropdown in the Task Metadata panel shows the saved status the moment the save confirmation appears instead of resetting to its placeholder until a reload. Integrations reading the task from the batch response should read `statusSlug`; the legacy `statusId` key is no longer emitted there.
  * **Task group queues load the full working set.** The reviewer queue on a task group's landing page and workspace loads up to 100 member tasks, so a group of that size shows every open task in the queue — ordered by priority, then creation date — and always offers the next one to pick up, even when the highest-priority tasks are already complete.
  * **The task template editor opens every template.** Templates created outside the editor — with `kdx`, in project-template YAML, or through the API — that have no `metadata.properties` set now open in the Project → Task Templates editor with defaults filled in, instead of an empty pane.

  **Orchestrator:**

  * **Scripted LLM calls carry model and cost attribution in the Explain Plan.** Every `llm.invoke` and `llm.invokeWithPromptRef` call made from a script step records the model id, provider (`anthropic_bedrock`, `cohere_bedrock`, or `bedrock`), input and output token counts, duration, and cost onto the Explain Plan step the script is currently inside — no script changes needed. When the call is not inside a step, the platform adds a completed `llm_call` step on the first document the script loaded, named from the call's `note` when you supply one, from the prompt reference for `invokeWithPromptRef`, and `LLM call` otherwise, so every scripted call is visible and attributable in the plan and in processing-step analytics. Cost is priced at call time from the AI Gateway model catalog — the same prices shown on the model list — and recorded once, so later catalog price changes never reprice history; where the orchestrator has no `AI_GATEWAY_URL` configured, or the model is not in the catalog, cost records as `0` and the call still succeeds.
  * **`recordLLMCall` records a usage entry only when it carries usage.** The step-authoring helper in the TypeScript, Python (`record_llm_call`), and Go step authors adds a model-usage entry when at least one usage field is set — model, provider, token counts, cost, duration, finish reason, request id, or in the Go author a sampling parameter such as `temperature` — and records the prompt and response bodies either way. A wrapper that only narrates the prompt and response text therefore leaves the platform's own model and cost record as the step's usage.
  * **`loadDocument()` returns one handle per document family.** Calling `loadDocument(familyId)` more than once in a script step for the same family now returns the same document handle, so data changes and processing steps written through different helpers all land on one document and publish as a single new version. Repeat loads of an already-loaded family do not count toward the script's maximum document-load budget. Scripts already written as though repeated loads return the same document need no changes.
  * **The orchestrator's built-in LLM model is configurable and works in every region.** AI task naming and the script-step `llm.invoke` binding call Amazon Bedrock with the model set by the new `LLM_SMALL_MODEL_ID` environment variable, which defaults to the `global.anthropic.claude-haiku-4-5-20251001-v1:0` cross-region inference profile. The global profile is accepted from any supported AWS region, so environments outside the US get working AI naming and scripted LLM calls with no configuration. Set the variable to pin a different Bedrock model id.
  * **Reprocessing from a step re-runs that step and everything downstream.** Choosing Reprocess on a completed step runs it again, including per-document work that had already finished, and discards the document versions the later steps produced, so the re-run starts from the same input the original run had; review tasks created downstream are removed and recreated when those steps run again. Retrying an activity's failures keeps the failed step's own completed per-document work but re-runs everything downstream, since its input may change. Documents that a routing step sends down another branch are shown as not processed on this branch, with no leftover results or errors from a run that no longer applies, and a run that a reprocess supersedes is marked Reprocessed rather than left looking as though it is still going.
  * **Finished module work survives a brief platform hiccup.** When a module runtime finishes a piece of work, it retries delivering the result with backoff instead of discarding it, so a transient `5xx`, `429`, or connection error no longer costs you the completed work; a rejection the platform will not accept (a non-`429` `4xx`) still stops immediately. Tune the retry window with `KDXA_RESULT_RETRY_MAX_SECONDS` (default `780` seconds, and never longer than the time left in the invocation) and the longest wait between attempts with `KDXA_RESULT_RETRY_BACKOFF_CAP` (default `30`); scheduler callbacks use the shorter `KDXA_CALLBACK_RETRY_MAX_SECONDS` (default `60`). Keep-alive heartbeats retry transient server errors too. The orchestrator also stays in service through a load spike, slowing down rather than dropping out, and still reports unavailable within a second when its database is genuinely unreachable.
  * **Module runtimes reuse a model package across steps.** A module runtime downloads and extracts each model package once and reuses it for every later step, so a long-running runtime keeps processing for its full lifetime instead of exhausting its temporary storage. Because the package is cached for the life of the runtime, a model implementation redeployed while a runtime is already running is picked up the next time that runtime restarts — a deploy that cycles the runtimes is unaffected.

  **CLI:**

  * **`kdx sync push` applies deletions inside the regions your files own.** Removing a key from a task template's `metadata.properties`, or removing a taxon — or any key inside one, such as a taxon's only `validationRules` entry — from a data definition's `taxons`, and pushing again brings the server in line with your file: the resource is reported as changed and the deleted content is removed. Only content your file actually states is compared, so a file that omits `metadata.properties` entirely leaves properties set in Studio untouched, while an explicit `metadata: {}` or `properties: {}` clears them, and a fully commented-out `taxons:` key is treated as unmanaged rather than as a deletion. Fields your file never mentions — `id`, `changeSequence`, `ref` and other server-maintained values — are ignored as before. Deletions converge when pushing to a current Kodexa API server; pushes to legacy servers keep the previous behaviour.
  * **`${org}` placeholders are resolved in module deployments, and any leftover placeholder stops the push.** Module payloads deployed by `kdx sync push` — including references embedded in a module's script, such as `${org}/my-taxonomy` — now have `${org}` substituted with the target organization slug, the same as every other resource type. After substitution, both the module and generic push paths check the payload: if any string still contains `${org}` (for example a malformed token such as `${org}-name` instead of `${org}/<slug>`), the push aborts with an error naming the resource and the field paths, instead of deploying the literal text and failing later at runtime.

  **Analytics:**

  * **Analytics task metrics now count by status type, not status name.** In the task, task-link and assignment analytics datasets, `completedTaskCount` / `completedTaskLinkCount` / `completedAssignmentCount` count tasks whose status is DONE-typed (or that carry a completed date), whatever the status is called — statuses named `reviewed`, `rejected` or `canceled` count as completed just like `done`. `openTaskCount` / `openTaskLinkCount` / `openAssignmentCount` count OPEN-typed statuses only, matching the task grid's **Open** quick filter and the task-group `openGroupCount`, and the `overdue*` metrics count any not-done task past its due date. Because IN\_PROGRESS and BLOCKED tasks are neither open nor completed, Open + Completed no longer necessarily equals Total — read each metric directly rather than deriving one from the others. Expect Completed to rise and Open to fall on existing dashboards.
  * **Extraction, classification, and chunking LLM calls are recorded as typed model usage.** Steps that record their usage as freeform `llm_usage` metadata now also carry structured `model_use` entries — model id, token counts, duration, and the provider where the model id identifies one — whenever the document is read, so an activity step's token totals, the step detail pane in the Explain Plan, and the model usage projected to analytics account for these calls alongside script-step calls. Existing documents pick this up on their next load or publish, with no reprocessing, and the freeform metadata stays in place for older readers. Cost is not yet attributed to these entries.

  **Chat:**

  * **Collapse the chat list to a rail in the Chat panel.** The list of chats beside the conversation can now be collapsed with the hide/show control in its header. Collapsed, it becomes a narrow rail that keeps **New chat** and your existing chats (shown as initials, with the active chat highlighted) one click away, and the conversation takes the reclaimed width — so a narrow panel dock still leaves room to read and reply. The collapsed/expanded choice is remembered per task or project workspace in your browser.
  * **Files offered by an agent appear as a labelled attachment card.** When an agent message offers a file to download, the message now shows an attachment card — file icon, file name (full name on hover), and a **Download** button — instead of a bare download-arrow emoji. Agents supply the file name through the new optional `name` attribute on the `<downloadDocumentFamily id="…" name="…" />` tag; a tag without `name` renders as a labelled **Download file** chip, so existing messages keep working. The control is a real button: keyboard-focusable and announced by screen readers with the file name. Copying a message as text or rich text keeps the file name in the copied content. If you author agents that emit this tag, add `name` so users can see which file they are about to download.

  **Activities:**

  * **The New Activity wizard enforces document group upload limits.** When a plan's document group sets `maxSize`, `maxPages`, or `hardMaxPages`, each file added in the New Activity wizard — by drag-and-drop or the file picker — is checked before it uploads. A file over `maxSize` or a PDF over `maxPages` prompts with **Include Anyway** / **Exclude Document**; a PDF over `hardMaxPages` is rejected with a **Document Too Large** notice and cannot be overridden. Page checks apply to PDFs only, every file in a batch is judged on its own, and a PDF whose page count cannot be read is allowed through. This brings the wizard in line with the New Task dialog, which already enforced these limits. Set the limits on the document groups in your activity plans to have them applied.

  **Manage:**

  * **Manage → Intakes lists only the current organization's intakes.** The Intakes grid is scoped by the organization in the URL, so it shows the right organization's intakes on a hard refresh, a direct link, or the first page after sign-in, and no longer carries a previously viewed organization's filter across sessions. Browsers holding an older saved grid filter are corrected automatically the next time the page opens.

  **Platform:**

  * **Sort the Projects list by Status, Owner, and Organization.** Clicking those column headers in Organization → Projects now sorts the list in either direction (a saved sort on one of these columns previously emptied the grid on every visit; affected users self-heal on next load). For API clients, `GET /api/projects` accepts `sort=status.status`, `sort=owner.firstName`, and `sort=organization.name` alongside the existing column sorts, and text search and filters keep working while one of these sorts is active.
  * **Signing in goes straight through.** Opening the app while signed out takes you to the sign-in page once and returns you to the page you asked for, with no repeated redirects and no stalled "Initializing Kodexa Platform" screen — including on slower or managed browsers.
  * **Notifications show their full message.** Success and error notifications across the workspace — a failed export or download, a copy-to-clipboard result, an activity step action, a channel stop — now display their explanatory text beneath the heading, including the specific reason an API call failed, instead of a bare "Error" or "Success" heading.
</Update>

<Update label="2026-08-06" tags={["Release", "2026.9", "Platform", "Knowledge", "Data Forms", "Chat", "Projects", "Orchestrator", "Tasks", "Activities", "Document", "SDK", "Breaking"]}>
  ### Release 2026.9

  Rollup of every customer-facing change in the 2026.9 GA release. The headline items: knowledge-set priority is now enforced and project-scoped sets finally apply, with feature links and item edits that reliably save; DeepSeek, Qwen 3, and Z.ai GLM models join the AI Gateway; and chat gains pre-built prompts with context-aware starter tiles. Create and update endpoints across the API now persist exactly what you send — explicit `false` and `0` included — with sparse updates and optimistic locking enforced consistently; this is the release's one breaking change — see the upgrade note under Platform.

  **Knowledge:**

  * **Project-scoped knowledge sets now apply to documents.** A knowledge set scoped to a project was silently never matched during document processing — the project was mis-derived from where the document was stored, so only organization-level sets ever applied. Matching now takes the project from the processing run itself, across every processing path, so a project-scoped set applies to documents processed in that project. Documents processed outside any project context are matched against organization-level sets only.
  * **Knowledge set priority is now enforced.** The 0–10 priority on a knowledge set (default 5) previously had no effect. Now, when several applied sets contribute knowledge to a document, their items are ordered by priority — highest first, with a stable tie-break — everywhere that order is visible: the knowledge context supplied to extraction, and the knowledge/instruction panel in the review form, which now shows the highest-priority set's instruction instead of whichever happened to load first. Priority only affects ordering; it does not change which sets match a document.
  * **Features linked to a knowledge set now actually save.** Linking features to a knowledge set — the set's feature palette — silently persisted nothing: after a reload the palette came back empty, and an expression condition that referenced one of the lost features rendered as an unresolvable picker. The `features` array on `POST`/`PUT /api/knowledge-sets` is now persisted: omitting the field (or sending null) leaves existing links untouched, an empty array clears them, and each entry may reference a feature by `id` or `slug` (validated against the set's organization — unknown references are rejected with a clear error). In the set editor, picking a feature through **Add Feature** in Advanced mode now also inserts it as a condition in the expression (duplicates are skipped), and Advanced mode shows a read-only **In Palette** chip list so palette membership is visible outside Simple mode.
  * **Bulk-loading knowledge items no longer drops items.** Creating, updating, or deleting many items of the same knowledge set concurrently — for example a CLI sync applying a set's items with parallel workers — could hit database deadlocks that failed most of the writes, leaving only a few items saved. Item writes to the same set are now serialized on the server, so parallel loads complete with every item intact.
  * **Reordering and deleting knowledge items now sticks.** In the knowledge set editor, items can be reordered by drag-and-drop, and the dropped order both renders correctly and is saved — so the order you arrange is the order the items are applied in. Deleting an item now persists too: previously the row disappeared from the editor but was never removed on the server, so it returned on the next reload. An item added and then removed in the same editing session is simply discarded without an error.
  * **Negated matching expressions no longer grow an extra "All of" group on every save.** Saving a knowledge set whose matching expression has NOT at the top level used to wrap it in a new "All of" group each time the editor reloaded it, nesting one level deeper on every save round-trip. NOT-rooted expressions are now kept as-is through save and reload, and they evaluate exactly as before.

  **Data Forms:**

  * **Notes on data values now save and stay in sync.** A note added to a data value through the Audit/Notes thread only ever lived in the current browser session — a save containing nothing but notes was silently discarded, so notes vanished on reload and never reached anyone else. Notes and their replies are now persisted with the document's change history: they survive reload, travel with the document, and appear for other people viewing the same document without a refresh. In the task workspace, the **Audit/Notes** context-menu item on a value now opens the Audit panel as expected — previously it did nothing in that layout.

  **Chat:**

  * **The AI Gateway now serves DeepSeek, Qwen, and Z.ai GLM models on Amazon Bedrock.** Modules and prompts that call LLMs through the Kodexa AI Gateway can now target non-Anthropic Bedrock model families — DeepSeek (including R1), Qwen 3 (including the vision-capable qwen3-vl family), and Z.ai GLM — through the same unified request, tool-calling, and streaming interface as every other provider. Per-family limitations are validated up front with a clear error (for example, a tool-use request against DeepSeek R1, which doesn't support tools, fails immediately instead of surfacing a raw provider failure mid-request). The gateway's model catalog can also declare per-model capability flags — `supports_tools`, `supports_vision`, `supports_documents`, and `reasoning_output` — surfaced on the model list so callers can pick a model by what it actually supports instead of parsing description prose. Streaming failures now arrive as a structured error event using the same error taxonomy as non-streaming calls, instead of error text spliced into the model's reply.
  * **Start a chat from a pre-built prompt, with starter tiles that match your context.** The chat panel's starter tiles now differ by context — task chats suggest task-shaped questions (summarize this task, explain the exceptions), project chats suggest project-level ones. Alongside them, a new **From prompt** button opens a picker of your organization's Prompt resources, so teams can publish curated, reusable prompts for common workflows. A prompt's title comes from its `name`, and its `metadata` carries `context` (`"task"` or `"project"` — which screen offers it), `category` (how the picker groups it), and `prompt` (the message body). Picking one starts a new chat named after the prompt with the body prefilled into the input — nothing is sent until you press send, so you can tailor it first. The button appears only when prompts exist for the current context; prompts are managed through the standard `/api/prompts` resource API.
  * **Copy an entire chat transcript from the chat header.** A copy dropdown in the chat header exports the whole conversation in your choice of Markdown, plain text, or rich text (formatted HTML with a plain-text fallback, so pasting into Word, Google Docs, or an email keeps the formatting). The transcript is chronological, with author and timestamp headers on each message, and excludes internal system traffic so it reads as the conversation you actually saw. It covers the messages currently loaded in the panel (the most recent 50); a copy that fails or produces nothing shows a notification instead of silently doing nothing.
  * **Platform administrators can see every user's chats on a task.** Chat lists in task views now show a platform administrator all users' conversations on that task — useful for supervision, QA, and support — while project-workspace chat lists remain scoped to each user's own conversations for everyone. The widened view affects visibility only: when an administrator starts a chat, or the platform opens one on their behalf, it is always their own — the broader view never causes a message to land in another user's chat.
  * **Chat agents hold their scope under adversarial prompting.** Chat agents are hardened against prompt-injection and jailbreak attempts: an agent now treats the capabilities enabled for its chat as its entire job, declines and redirects out-of-scope requests, resists instruction-override attempts (persona swaps, "developer mode", "ignore previous instructions"), and will not reveal its internal instructions or configuration. Content the agent reads from documents and tool results is treated as data, never as instructions. These boundaries are enforced by the platform rather than just requested of the model — a tool call outside the chat's enabled capabilities, or a file access outside the agent's working scope, is blocked before it executes.
  * **Renaming a chat now saves reliably and confirms the result.** A rename could previously fail with a spurious permission error, or overwrite changes someone else had just made to the same chat (such as its sharing settings) — and a failure gave no visible feedback at all. The rename now updates only the chat's name and shows a success or failure notification; on failure the rename stays open so you can correct and retry.

  **Platform:**

  * **Create and update endpoints now persist exactly what you send — explicit `false`, `0`, and other zero values included (breaking).** Previously a generic `PUT` silently dropped any field whose value was `false`, `0`, or empty: the request returned 200 OK but the value never changed — so deactivating a user, switching a boolean option off, or zeroing a numeric setting could be a silent no-op. `POST` had the twin problem: an explicit `false` or `0` on a field with a server-side default was overwritten by the default. Both paths now track which keys your request body actually contained and write exactly those fields: explicit zeros persist, omitted fields are left untouched (reliable sparse updates), sending `null` clears a nullable field, and echoing back a `GET` body is a safe no-op. The write path is also hardened: system-managed and ownership fields (`id`, `uuid`, `createdOn`, `createdByUserId`, `organizationId`, `projectId`, and soft-delete state) are ignored if sent in an update body, and `changeSequence` is never written directly — it serves only as the optimistic-lock token; a request body that is not a JSON object, or that repeats the same field under case-variant keys, is rejected with `400`; uniqueness and reference violations now return clean `400`/`409` responses instead of `500`s; and optimistic locking is enforced whenever the body carries a non-null `changeSequence` — including `0` on a never-updated resource — so a stale save returns `409` instead of silently overwriting newer data.

    **Upgrade note.** Audit integrations that `PUT` full or hand-built objects: fields carrying `false`/`0` are now written rather than ignored, and calls that used to return `200` can now return `400` (non-object body, duplicate case-variant keys) or `409` (stale `changeSequence`, uniqueness conflict). To leave a field unchanged, omit it from the body; to clear a nullable field, send `null`. Values sent for `createdByUserId`, `organizationId`, or `projectId` on update are ignored. On the project-assistant endpoints, an update that changes `name` without sending `slug` now re-derives the slug from the new name.

  * **Document-store and data-store bodies are now complete, stable, and safe to round-trip.** Fetching a store could previously return different bodies for the same resource — sometimes missing inner metadata keys such as `indexed` — and saving a store back (an edit in the UI, a full-object `PUT`, or a `kdx apply`) could silently wipe inner content-metadata settings such as `indexed`, `documentProperties`, and `labelExpressions`. Store bodies now carry the nested store metadata intact under a new `contentMetadata` key (the existing flattened keys remain for compatibility), reads return the same body every time, and round-trip saves preserve every setting.

  * **Project slugs are now guaranteed unique within an organization.** Creating or renaming a project whose slug would collide with another project in the same organization now automatically appends a numeric suffix (`-1`, `-2`, …) instead of allowing a duplicate — renames previously had no protection, so two projects could end up sharing a slug and slug-based references became ambiguous. Uniqueness is now also enforced at the database level. A `slug` you supply on create is honored (and suffixed only on collision) instead of being silently replaced with a derived one, and re-saving a project under its own slug stays idempotent. Creating a project from the CLI now leaves slug derivation to the server, so project names with spaces or punctuation no longer fail slug validation.

  * **Deleting an organization no longer fails when it has an audit history.** Hard-deleting an organization previously hit a server error once audit records existed for it. The organization's audit-trail records are now removed as part of the delete, so the delete completes cleanly.

  **Projects:**

  * **Triggers gain two new event kinds and can ship inside project templates.** `document_locked` (a reviewer locks a finished document) and `knowledge_set_updated` (a knowledge set changes) join `task_created`, `task_status_changed`, `activity_completed`, and `manual` as accepted trigger event kinds — these are what let a project start an activity plan in response to review activity. Project templates can now also declare a `triggers` array (`slug`, `name`, `eventKind`, optional `eventFilter` and `inputMapping`, `activityPlanRef`, `enabled`); the triggers are created at project creation after the template's activity plans are bound, and re-applying a template skips triggers that already exist instead of failing. Separately, `PUT /api/triggers/{id}` now fully validates the update — an unrecognized event kind, malformed activity-plan reference, or malformed filter is rejected instead of silently persisted.
  * **Required and pattern validation on project-template options is now enforced at project creation.** A template option (in `options` or `dataOptions`) marked `required: true` must be filled in before the New Project dialog allows Create — previously the required asterisk was cosmetic and the project was created with the option blank. Options can also declare `properties.pattern` (a regular expression the value must match) with an optional `properties.patternMessage` shown inline when the value doesn't match; validation errors appear under the field and above the action buttons, and the dialog switches to the first tab with a problem. An option with a defined `default` counts as satisfied, and developer options are only validated in Studio, where they are shown.
  * **Surface a template option on the first tab with `showOnPopup`.** A project-template option flagged `showOnPopup: true` now renders on the New Project dialog's **Details** tab, directly beneath the project name and description, instead of behind the Developer Options or Data Options tab — so a required option is visible before you can hit Create. After the project exists, the same flag places the option on the **General** tab of Project Settings, and a Settings tab whose options are all flagged is dropped entirely. Options without the flag stay on their usual tabs (in the New Project dialog the original tabs remain, minus the flagged options).
  * **Values entered for template options when creating a project now save reliably.** Typing a value into a template option in the New Project dialog could show nothing or silently drop the edit, and even when a value was submitted the server could discard it while applying the template's options — so a project could end up created without the values you entered on the form. Option values now display as you type, validate, and persist onto the created project exactly as entered; the create dialog also no longer modifies the shared template definition.
  * **Project Settings changes no longer appear to revert after a page reload.** Saving project settings — name, description, or template option values — always persisted on the server, but reloading the page could show the old values for up to a day because the browser kept serving a stale locally cached copy of the project. The local cache is now updated on save, so saved changes survive a reload immediately.

  **Orchestrator:**

  * **CREATE\_TASK steps can name and stamp tasks with runtime placeholders.** The `taskData` of a CREATE\_TASK step now resolves placeholders at the moment the task is created: `${activity.title}` (the activity's title at that point, after any automatic or manual rename), `${project.name}`, `${project.id}`, and `${project.options.dataProperties.<key>}` (a value entered in the project's data properties, as defined by the project template). Placeholders work in the task's `title` and `description` and in top-level string values under `taskData.properties` — so one plan shared across many projects can give each child task a context-specific name (e.g. `"title": "Review ${activity.title}"`) or copy a project-scoped identifier onto every task it creates for downstream filtering. If `${activity.title}` or `${project.name}` cannot be resolved, the text is left unchanged; a `dataProperties` placeholder always resolves — to an empty string when the key is unset or not a simple value — so unresolved tokens never leak into task properties.
  * **The activity Logs tab now shows the right document's logs.** When an activity step runs once per document, the Logs tab in the activity status dialog could display logs belonging to a different document than the one you were focused on — reopening the dialog on another document, or clearing the document focus, could leave it pinned to the previous document, and background refreshes reset the execution you had picked in the dropdown. The Logs tab now follows the focused document as it changes, preserves your dropdown selection across refreshes, and shows a clear empty state instead of another document's logs when the focused document has no execution for that step.
  * **Task operations no longer fail when a history entry can't be recorded.** Actions such as locking or unlocking a task, or changing its team, also record an entry in the task's activity history. Previously, if that history write failed, the whole operation failed with a server error — task locking could break entirely. The history record is now best-effort: the operation itself completes normally and a failed history write is logged server-side instead of failing the request.

  **Tasks:**

  * **The explanation entered when unlocking a task is now recorded.** Unlocking a locked task prompts for an explanation, but the text was being discarded — the unlock went through with nothing attached. The explanation is now saved on the task's unlock activity: the activity text reads `Task unlocked: <your explanation>` and the reason is available on the activity record through the task-activities API, so audits of who unlocked a task and why are complete. (The `reason` field on the unlock endpoint's request body is optional; a blank explanation still unlocks.)

  **Activities:**

  * **Creating an activity from inside a project no longer asks you to pick the project.** The New Activity wizard opened from a project's Activities tab now starts with that project already selected, skipping the redundant project-selection step — matching how the wizard already behaved when launched from a document view.
  * **The My Projects filter in the New Activity and New Task wizards no longer flips on its own.** The filter used to default to on and then silently uncheck itself whenever the project list came back empty, so it appeared randomly checked or unchecked from one open to the next — and could quietly widen your project list without you asking. It now defaults to off and only changes when you change it. When the filter is on and you don't own any of the listed projects, the wizard now says so and suggests unticking **My Projects** to see all projects, instead of clearing the filter behind your back.

  **Document:**

  * **Edits made right after saving a document are no longer discarded.** After a save in the review workspace, the platform's periodic change check could mistake your own save for an outside change and force-reload the document, silently throwing away anything you had entered since the save. Your own saves are now recognized and the document is left alone. When a document genuinely is updated outside your session it still reloads to the latest version, but you now get a prominent warning whenever that reload discarded unsaved edits, instead of a quiet refresh. For API clients, each result in the batch-update response now includes a `contentObject` object carrying the saved content object's `id` and `changeSequence`, so a custom client polling for changes can distinguish its own save from an external one the same way.

  **SDK:**

  * **Python SDK objects stay current after writes.** Calling `create()`, `update()`, or `deploy()` on an SDK entity now refreshes the object in place from the server's response — previously the local object kept its pre-write state (including a stale `changeSequence`), so a second `update()` on the same object failed with a `409` conflict. Task status and assignee updates made through the SDK also now send the task's own change sequence, so they participate correctly in optimistic locking.
</Update>

<Update label="2026-07-23" tags={["Release", "2026.8", "Platform", "Tasks", "Task Groups", "Task Templates", "Knowledge", "Data Forms", "Analytics", "Orchestrator", "CLI", "Document", "Formulas", "Chat", "Breaking"]}>
  ### Release 2026.8

  Rollup of every customer-facing change in the 2026.8 GA release. The headline items reach across the platform: a new `POST /api/analytics/embed-token` endpoint for embedded, per-tenant analytics, a dedicated task-lock feed on the CDC data lake, and keyboard shortcuts you can bind to task-template actions. Scripts can now stamp `taskProperties` onto the tasks they create, and the Knowledge expression builder gets a broad overhaul — self-healing matching expressions, reversible negation, and safer Simple/Advanced mode switching. The CLI hardens up with real server-side `--dry-run` validation, credential redaction in debug output, and pipe-safe `-o json`. Alongside those, this release lands a wide set of Data Forms, Document review, and Tasks fixes. One breaking change lands on the task-status API; see the upgrade note.

  **Analytics:**

  * **New `POST /api/analytics/embed-token` endpoint for embedded, per-tenant analytics.** Authenticated callers can exchange their Kodexa credential for a short-lived, tenant-scoped RS256 JWT and query the standalone analytics service directly. The token's organization scope is derived server-side from the caller's team/organization assignments (never client-supplied), and every dataset it grants is row-filtered to those organizations, so a token can only ever read its own tenants' rows. The response returns `{ token, expiresAt, models, orgs }`, with token lifetime capped at 15 minutes. The minter is opt-in via the `analyticsEmbed` config block (`enabled`, `issuer`, `audience`, `keyId`, `tokenTtlSeconds`, `models`, `objects`, and `rowFilterField` — defaults to `org_slug`), while the RS256 signing key is supplied out-of-band through the `ANALYTICS_EMBED_SIGNING_KEY_PEM` environment variable. When the minter is disabled or the key is missing/unparseable, the endpoint returns `503` and platform boot is unaffected.
  * **Task locks now stream to the data lake.** Whenever a task is locked, the platform now mirrors a task-lock record to your CDC data lake under a new `task-locks/` prefix — a dedicated feed, kept separate from the existing work-session telemetry — so you can report on when tasks were locked and by whom. Each record includes `taskId`, `lockedAt`, `lockedById`, `lockedByEmail`, `taskStatus`, `projectId`, the task's `documentFamilyIds`, and the task `properties` object, and records the user who performed the lock (omitted for system or API-key callers). The write is best-effort and uses your existing data-lake configuration.
  * **More accurate Work Sessions durations, immune to client clock skew.** The **Work Sessions** dataset now measures each session's wall-clock time from the browser's own elapsed-time reading rather than the difference between a client and a server timestamp, so durations stay accurate even when a machine's clock is offset from the server. Active/engaged time is now reported raw instead of being silently capped to the wall-clock value, and two data-quality measures — **Skew-Invalid Wall Count** and **Avg Client Clock Skew (ms)** — surface sessions whose wall-clock reading was thrown off by client clock skew, so distorted readings are visible rather than hidden. **Focused Ratio (%)** is computed only from sessions that have both a valid wall-clock and an active measurement, and each session is counted once (intermediate saves within a session are collapsed to the final save) rather than double-counted. Sessions recorded before active-time capture existed are excluded from the active metrics instead of being averaged in as 0% engaged, and the **Total Active (ms)** / **Avg Active (ms)** measures are renamed **Total Focused (ms)** / **Avg Focused (ms)**.

  **Tasks:**

  * **The Task API now reports when and by whom a task was locked.** The Task object returned by the tasks API now includes `lockedAt` (the lock timestamp) and `lockedById` (the user who locked it) alongside the existing `locked` flag.
  * **The bulk "Lock" action on the Tasks grids now actually locks.** Selecting tasks and choosing Bulk Actions → Lock — in both the project and organization Tasks views — previously did nothing (the grid just refreshed as though it had worked). It now prompts for confirmation and locks each selected task. Separately, opening a locked task while "Assign on open" is enabled no longer pops a spurious "Unexpected Exception" error: the auto-assign is skipped for a locked task (which the server rejects) and the task simply opens.
  * **Setting a task's status from the UI now saves reliably.** Changing a task's status — through the bulk **Set status** action, the task details panel, or the status dropdown in the tasks grid — now persists correctly. Previously a bulk status change could clear the task's status (leaving the badge blank), a status change made in the task details form could be silently dropped, and two consecutive status changes on the same task could fail with a conflict error.
  * **Task actions now wait for a form's bridge lookups to finish before they can be triggered.** A task action such as Approve or Reject could previously fire while a form's service-bridge lookups were still resolving and writing back an auto-populated value, letting a task complete with a value that had not yet been saved. Approve, Reject, and other task actions are now disabled while any of the form's bridge lookups are still in flight, so an action only runs once the form's auto-populated values have settled.
  * **Task, task-group, and activity lists no longer show "nothing here" while a stale, invisible filter hides pending work.** In both organization and project views, a list could report nothing to do while work was actually pending, with no filter visibly selected — the restored query kept filtering by a stale clause while the toolbar controls reset to blank. Each list now persists its toolbar inputs (quick filter, facets, project selector, date/running filters) and rebuilds the query from them on load, so the visible filters and the results always agree; pre-existing stale state self-heals on first load.
  * **Task-status API field renamed `statusId` → `statusSlug` (breaking).** The request body for `PUT /api/tasks/{id}/status` now takes `statusSlug` — the org-scoped task status slug — instead of `statusId`. The value was already a status slug rather than a UUID; only the field name changed, so callers stop mistakenly sending a status UUID that could never be resolved.

    **Upgrade note.** Update any integration calling `PUT /api/tasks/{id}/status` to send the status under `statusSlug` (a slug such as `in-review`), not `statusId`. Requests still sending `statusId` are rejected with `statusSlug is required`.

  **Task Groups:**

  * **Task groups now record how their assignee was set.** Task-group responses — including the list endpoint and the take-next claim response — carry a new `assignmentType` field: `TAKE_NEXT` (claimed from the kiosk take-next widget), `SELF_CLAIM` (a user claimed the group themselves), or `MANUAL` (assigned by someone else). It is set on every assignment path, cleared when the assignee is cleared, filterable on the list endpoint, and included in the `task_group.assigned` event payload and the group's history. Self-claiming or self-releasing a group is now also correctly checked against the `claim` / `release` permission instead of always requiring `assign`.
  * **Clear filters on Task Groups.** The project and organization **Task Groups** tabs now show an active-filter summary row with a **Clear filters** button (matching the Activities tab), so you can see at a glance which filters are applied and reset them all — search, quick filter, and facets — in one click.
  * **Clearing a task group's team now works.** Removing the team from a task group via `PUT /api/task-groups/{id}` (sending an empty `teamId`) now clears the team instead of failing with a server error. Omitting `teamId` still leaves the team unchanged.

  **Task Templates:**

  * **Bind keyboard shortcuts to task-template actions.** A task-template action can declare a `shortcut` (and an optional `shortcutAltKey` alternate combination) in its `properties`, and the template itself can declare `saveShortcut` / `cancelShortcut` (with `saveShortcutAltKey` / `cancelShortcutAltKey`) for its default Save and Cancel buttons. Shortcuts are purely declarative — there are no built-in default keys — and they register per task and clear automatically when the task closes. The keyboard-shortcuts help dialog (`Ctrl` / `⌘` + `/`) now lists these alongside data-form shortcuts in an aligned Windows / Mac layout, and a registered shortcut that uses a modifier now fires even while a text field is focused.

  **Orchestrator:**

  * **Scripts can stamp properties onto the tasks they create.** A SCRIPT step's return value may now include a `taskProperties` object; its keys are merged into the `properties` of any CREATE\_TASK step that directly depends on that script, at creation time — so the task carries them from the moment it exists. The gating script is the last code to run with document context before the task exists, making it the natural place to compute per-document grouping or routing keys. The merged properties appear on every task API response and are queryable with the standard filter DSL (e.g. `filter=properties.invoiceRegion: 'EMEA'`). Static `taskData.properties` declared on the CREATE\_TASK step apply first and script-supplied keys win on conflict; if several upstream scripts supply `taskProperties` they merge in completion order (latest completion wins per key). Only steps that directly depend on the script receive the properties — there is no transitive propagation. Returning a non-object `taskProperties` from a script fails that script step with a clear error; the merge step itself is failure-soft — a merged properties blob over 256KB is dropped with a warning and never fails task creation. This is the creation-time counterpart to `tasks.setProperties`, which you still use to update a task that already exists.

  **Knowledge:**

  * **Knowledge sets recover from corrupt matching expressions.** A knowledge set's feature-matching expression could accumulate broken conditions — a blank condition, a feature condition with no feature chosen, or a leftover condition after un-negating a group. Any one of them evaluated as false and pulled down the whole **All of** group it sat in, so the set silently stopped matching every document. Broken conditions are now dropped automatically when the expression is evaluated, so an affected set starts matching again on the next processed document with no need to re-open and re-save it. The expression builder no longer creates these conditions, and any leftover unrecognised condition now appears as a clearly-labelled, removable **Invalid condition** row instead of an empty box you can't delete.
  * **Deleting a knowledge set now stops it matching documents.** Previously a deleted knowledge set kept matching every newly processed document indefinitely, because the eligibility lookup did not exclude deleted sets. Deletion now removes the set from document matching as expected.
  * **Expression builder handles negation and mode switching predictably.** A NOT block now carries the same **All / Any / NOT** control as any other group, so negation can always be turned back off — previously choosing NOT hid the control and left the block stuck. Negating an already-negated condition now removes the negation instead of double-wrapping it. The **Simple / Advanced** toggle follows the expression itself: switching from Advanced to Simple on an expression that uses NOT or nested groups asks for confirmation first (Simple mode keeps only a flat list of features), and adding a feature to the palette no longer bounces you into Advanced mode and traps you there.
  * **Inactive features stay visible in the feature palette.** Features linked to a knowledge set but deactivated are now shown in a distinct **Inactive features** group — struck through and removable — instead of being hidden. Hiding them could make a palette look empty right after adding a feature, with no way to see or clear the dead entry. The quick-create / feature picker also no longer offers deactivated features, since selecting one appeared to succeed but then matched nothing.
  * **Project-scoped knowledge sets are labelled correctly.** A knowledge set scoped to a project no longer mislabels itself as **Organization Level** in the sets list and detail panel when the project object hasn't been loaded — it now recognises the project scope and shows **Project-scoped** (with the project name when available). The knowledge-set API also returns the associated project, so the correct name is shown. Previously the false 'Organization Level' label could hide a mis-scoped set from its author.
  * **Deleting a knowledge set no longer shows a spurious error.** Deleting a knowledge set used to send the delete request twice, so it reported both a success and an error at once — the second request hit the already-deleted set. Deletion now runs exactly once and reports a single result.

  **Data Forms:**

  * **Transposed grid rollup cards gained toolbar and header polish.** An **expand/collapse-all** button toggles every rollup parent row at once and re-applies after the grid rebuilds; an **auto-filter** toggle (on by default) populates the description/value filters from whatever you click in the document viewer, with numeric content driving the value filter and text driving the description filter; and column headers now show the **full document name**, truncated responsively with the full name and summary in a hover tooltip, and the lock and knowledge icons kept snug beside it. Row numbers also stay stable while a search or filter is active, and columns open at a uniform default width regardless of document-name length.
  * **Adding to and editing a transposed grid rollup now behaves correctly around locked documents and on save.** The add (+) button only appears on columns whose document matches the active selection's source document, so you can no longer add into the wrong document's column; inline description edits are blocked on rows whose contributors all live in locked documents and skip locked columns when saving mixed-lock rows; and showing/hiding columns is still allowed for locked documents (locking blocks data edits, not view-only preferences). Separately, a newly added rollup group is no longer silently merged into an existing group, and a row added via the popover no longer vanishes, when the document is saved.
  * **Opening a grid's column menu no longer spuriously opens the first row's dropdown.** On editable data-object grids, opening or dismissing a column-header menu or the Choose-Columns panel could pop open the first row's SELECTION dropdown. That no longer happens — the first row's dropdown opens only on a real click or keyboard focus, not when the grid shifts focus internally.
  * **A stray dropdown no longer floats in the top-left corner when a form loads.** On load, a form auto-activates its first document-sourced field so it is ready for copy-from-document. When that field was a SELECTION dropdown on an inactive, hidden `v2:tabs` panel, activating it could leave a stray dropdown floating in the top-left corner. Activation now skips fields that aren't currently visible, so the stray dropdown no longer appears.
  * **Read-only SELECTION fields now display the option label instead of the stored code.** An attribute editor with `readonly: true` bound to a SELECTION taxon rendered the raw stored code rather than the chosen option's label. Read-only SELECTION fields now resolve and show the option label, falling back to the raw value only when no option matches or the options have not loaded yet.

  **Data Definitions:**

  * **Show a field's full value on hover in grids.** A new per-taxon display flag, `typeFeatures.showFullOnHover: true`, makes a grid column show its full cell value in a hover tooltip — useful for prose-length fields like AI explanations, notes, or comments without widening the column. Enable it from the taxon editor ("Show full text on hover") or set it in the data-definition YAML. Empty cells show no tooltip.
  * **`changed:dataAttribute` subscriptions fire when a value is first entered, not only on later edits.** An event-subscription script — and any derive logic it drives — now runs the moment a reviewer sets a value on a previously-blank attribute, matching the documented "fired when a data attribute changes" contract. Previously the subscription fired only on subsequent edits, so a reviewer's first pick into a blank field could leave dependent fields un-derived until the value was cleared and re-entered.

  **Formulas:**

  * **Formula-typed fields are computed and stored during server-side document processing.** When a document is opened outside the browser review UI — in server-side extraction and transform steps — FORMULA taxon values are now re-derived against current inputs and persisted, including creating formula attributes that don't yet exist, before validation, conditional-formatting, and selection-formula passes run. This brings server-side processing in line with the browser, so downstream consumers such as validations, exports, and analytics see correct formula values without a reviewer first opening the document.
  * **Extraction-time validations and formulas now resolve group references correctly.** When validations and formulas run as part of model processing — before the extracted data has been persisted — group references now resolve against the in-memory data tree instead of relying on database IDs that aren't assigned yet. Previously a bare group reference could resolve to nothing at extraction time, so a rule such as `isnull({group})` fired a false failure on clean data, and a validation exception on a nested group or attribute was anchored to the top-level object instead of the row it applied to. Exceptions now attach to the correct nested data object and attribute, and group-scoped formulas evaluate correctly.

  **Document:**

  * **Document-level validations added in one processing step now evaluate in later steps.** When a step adds document-level taxon validations — for example a transformer calling `set_validations` — and extraction later runs in a separate process, those validations are now read back and evaluated, so the expected data exceptions are raised. Previously they were held only in memory, so a freshly opened document saw an empty set and raised nothing. Such validations are now also included in JSON export.
  * **Grouped tags no longer corrupted when converted to and from features.** Converting tags to and from their feature representation — for example exporting a document to JSON and re-importing it, or adding tags through the feature API — now preserves the full tag payload, including the UUID-based grouping identifiers, so grouped and owned tags are no longer rewritten as ungrouped, ownerless rows that extraction would fragment into spurious data objects. Camel-case, snake-case, and legacy field-name variants of the payload are all accepted. Removing a tag from a node now removes every instance of that tag name (not just the first), deletes the underlying tag once nothing else references it, and is a silent no-op when the node doesn't carry the tag (it previously raised an error) — so loops that strip a node's tags one by one run cleanly.
  * **Copied text reads in reading order on rotated pages.** Selecting text on a rotated page — most visibly a page turned 180° — and using **Copy value from document** (or a native Ctrl/Cmd+C copy) now returns the text in visual left-to-right order instead of reversed. The highlight/overlay layer also stays aligned to the page across every rotate path — toolbar button, keyboard shortcut, and form-shortcut bridge.
  * **The document viewer no longer gets stuck on a permanent loading spinner for dense pages.** A page that takes a couple of seconds to render — for example a full broadsheet with thousands of words — could previously leave the viewer stuck on an endless loading spinner instead of displaying. These pages now settle and render correctly.
  * **`adopt_children(replace=True)` no longer discards nodes nested under the children it replaces.** When the nodes being adopted were descendants of the children being replaced — for example re-flattening a line whose words sit under intermediate column nodes — the replace step used to cascade-delete them, leaving empty phantom children that broke later processing. Adoptees are now re-parented before the replace-removal, so they survive intact.
  * **Document-engine updates apply cleanly on reload.** When a new engine version ships, the in-app "Update Available" reload now reliably fetches the fresh engine instead of re-serving a stale cached copy — which could loop the update prompt or force a manual "Empty Cache and Hard Reload." A version mismatch detected after boot now self-heals by re-fetching the engine once and rebooting, with no user action required.
  * **Rich-text editor toolbar gains tooltips and accessible labels.** Every button on the markdown editor toolbar — bold, italic, link, heading, ordered/bullet lists, undo/redo, and the table controls — now shows a hover tooltip and carries an accessible label, so the icon-only controls are clearer on hover and readable to screen readers.

  **CLI:**

  * **`kdx sync push --dry-run` now validates against the live server.** A dry-run push previously computed its diff entirely client-side and never exercised the platform's write path, so metadata that would fail server validation still passed a dry run and only failed on the real push. Against a 2026.8 or newer server, `--dry-run` now submits each would-be create/update with the new `?validate=only` request parameter: the server runs the full write path — validators at enforced strength plus slug, foreign-key, and common-rule checks — and persists nothing (returning `200 {"valid": true}`, or an RFC 9457 problem+json `errors[]` body on rejection). Error-severity findings are reported per resource and make the command exit non-zero, so a CI job can gate metadata-repository pull requests on `kdx sync push --dry-run`. Against older servers the dry run automatically falls back to the previous client-side diff. The `?validate=only` parameter can also be used directly against any write endpoint to validate a payload without persisting it.
  * **Credentials are redacted from CLI diagnostic output.** Error messages and `--debug` request/response logs now mask credential-bearing values and headers — API keys, tokens, secrets, passwords, passphrases, private keys, and connection strings — before they are printed, so a failed apply or a debug session no longer writes live credentials into terminal or CI logs. Redaction is display-only; the actual request payloads sent over the wire are unchanged.
  * **`-o json` output is now complete and pipe-safe.** List commands run with `-o json` now return every page of results instead of only the server's first page (about 20 rows), and all informational and debug logging is written to stderr. As a result `kdx get … -o json | jq` pipelines are no longer silently truncated or corrupted by log lines mixed into stdout. Pinning an explicit `page` parameter keeps the single-page behavior.
  * **API discovery cache is keyed per server.** The CLI's cached API discovery spec is now stored per server URL with a 24-hour freshness window. Switching between profiles that point at different servers no longer runs commands (run, describe, validation, resource operations) against another server's cached API shape.

  **Chat:**

  * **Copy chat messages in the format you need.** The copy button on a chat message is now a dropdown with three choices: **Copy as Markdown** (the raw message source, the previous behavior), **Copy as Text** (the rendered message as plain text), and **Copy as Rich Text** (formatted HTML with a plain-text fallback, so pasting into Word, Google Docs, or an email keeps the formatting). Rich-text copy falls back to a plain-text copy where the browser clipboard API isn't available.

  **Activities:**

  * **AI-suggested activity names no longer surface error text as the title.** The activity naming assistant is now instructed never to place an error message or explanation in the title field, so a naming hiccup no longer produces an activity titled with raw error text.

  **Platform:**

  * **Search users in the admin Users view.** The admin **Users** view now has a free-text search box that matches across a user's email, first and last name, job title, and business group, resolved server-side via `GET /api/users?query=`. Existing users are searchable by the newly searchable title and business-group fields immediately.
</Update>

<Update label="2026-07-16" tags={["Release", "2026.7", "Platform", "Data Forms", "Formulas", "Activities", "Intake", "Analytics", "Performance", "Taxonomy", "Breaking"]}>
  ### Release 2026.7

  Rollup of every customer-facing change in the 2026.7 GA release. The headline items span the form-building surface: a new `ifBlank()` formula function and steadier formula recalculation, new data-form controls for AI explanation text and selection hints, and keyboard-driven detach / dock / zoom of the document viewer. Alongside those it brings a queryable **Work Sessions** analytics dataset, a faster task-open path, a wave of **Activities** polish (plan provenance, live progress, a smoother New Activity wizard), and data-modeling safeguards around taxon external names. One breaking change lands in intake — post-upload scripts can no longer overwrite reserved document fields; see the upgrade note.

  **Data Forms:**

  * **Render an attribute as an AI explanation callout.** A new `editorOptions.displayAs: "explanation"` renders an attribute as a wrapping AI callout that preserves line breaks and grows to fit its content — for AI rationale text that used to clip inside the fixed-height value box.
  * **Selected-option hint shows beneath a closed SELECTION dropdown.** A `SELECTION` editor now shows the chosen option's hint (plain text or markdown) below the closed dropdown, so the guidance for the current choice stays visible. It is on by default and opt-out via `editorOptions.showSelectedHint: false`, and is suppressed inside grid cells.
  * **Drive the document viewer from form shortcuts: detach, dock, zoom.** New `bridge.viewer` methods let declarative form shortcuts control the document viewer the way the toolbar buttons do: `viewer.detach()` pops the viewer out into its own window, `viewer.dock()` re-docks it, and `viewer.zoom("in")` / `viewer.zoom("out")` change the zoom level.

  **Formulas:**

  * **New `ifBlank()` formula function.** `ifBlank(a, b, …)` is a blank-aware coalesce: it returns the first argument that isn't blank, and an empty string when every argument is blank. Unlike `ifNull`, it treats an empty or whitespace-only string as blank — so a chain of references falls through to the next candidate — while `0` and `false` pass through unchanged.
  * **Aggregates over a missing reference settle to zero instead of erroring.** A multi-part reference inside a formula that can't be resolved now yields an empty list rather than failing the calculation, so an expression like `ifnull(sumifs(…), 0)` settles cleanly to `0`. This removes the transient red "formula calculation failed" recalc toast that used to appear while the referenced group didn't yet exist.
  * **Parent-relative selection formulas resolve on create.** A selection formula that reads a parent value with a `{../Field}` reference now resolves to the real value the moment a row is created, instead of an empty list. Previously a service-bridge-backed selection could fire with an empty argument on a brand-new row; the parent value is now available on first evaluation.

  **Activities:**

  * **New Activity wizard loads the full plan before the details step.** When you pick a plan, the wizard now fetches that plan's full body before rendering the details step, so the document-upload panel appears right away — no more going Back and re-selecting the plan once a background refetch lands.
  * **Activity-plan provenance on tasks and activities.** The task details sidebar now shows the Activity Plan that spawned the task (hidden when a task wasn't spawned from a plan), and the activity dialog header shows a "Plan" chip naming the plan.
  * **Activities grid progress reflects live step status.** The Activities grid's progress column and counters are computed from live step statuses (completed / running / failed) instead of lagging summary fields, so progress tracks what's actually happening.
  * **Spawn-time validation errors are surfaced.** When an activity plan fails validation as it's spawned (for example, from a script step or a completed-activity trigger), the specific validation error is now surfaced and recorded instead of the spawn silently rolling back.

  **Analytics:**

  * **New "Work Sessions" analytics dataset.** A queryable Work Sessions dataset reports wall-clock time versus actual active/engaged time per task, including an active-ratio percentage — so you can see how long work really takes against how long a task was open.

  **Performance:**

  * **Opening a task workspace is faster.** A task's fetches are now parallelized and de-duplicated, the next task in a group is warmed in the background, and the open reuses data that is already loaded — so tasks open noticeably faster, especially when advancing through a queue.

  **Intake:**

  * **Post-upload intake scripts can no longer overwrite reserved document fields (breaking).** A post-upload intake script that returns a reserved structural metadata key — `source`, `uuid`, `version`, `labels`, `mixins`, or `statusId` — now fails the upload and rolls back the transaction, instead of silently overwriting the document's structural fields. These keys back dedicated document fields (for example, `source` holds the document's original filename).

    **Upgrade note.** If an intake's post-upload script sets any of `source`, `uuid`, `version`, `labels`, `mixins`, or `statusId` in its returned metadata, those uploads will now fail rather than silently overwrite. Rename the offending field to a non-reserved key (for example, `documentSource`) before upgrading.

  **Taxonomy & extraction:**

  * **Blank taxon external names default to PascalCase over the taxonomy MCP tool.** When an agent creates a taxon with a blank external name via the taxonomy MCP tool, the external name now defaults to PascalCase of the internal name (for example, `invoice_date` becomes `InvoiceDate`). An external name the agent supplies is never overwritten.
  * **Clear error when a taxon has no external name on export.** Data-object (JSON) export now fails loudly and names the offending taxon when a taxon has no external name, instead of exporting under an empty key or a silent fallback; XML export enforces the same.
  * **`skipExtraction` taxons are excluded from chunking again.** Taxons flagged `skipExtraction` are once more excluded from document chunking and extraction, restoring the intended contract.
</Update>

<Update label="2026-07-02" tags={["Release", "2026.6", "Platform", "Workflow", "Manage", "Data Forms", "Task Groups", "Analytics", "Performance", "Document", "Orchestrator"]}>
  ### Release 2026.6

  Rollup of every customer-facing change in the 2026.6 GA release. The headline items: a new top-level **Manage** administration area that gathers every org-admin surface in one place, and a **Kodexa Workflow MCP connector** that exposes activities, tasks, and task-groups to claude.ai as a remote OAuth connector. Alongside those, this release brings a wave of review-experience performance work, a broad set of Data Forms V2 shortcut and grid fixes, task-group polish, opt-in user presence & activity tracking, and analytics and orchestration fixes.

  **Manage:**

  * **A dedicated Manage area.** Organization administration now lives in its own top-level **Manage** area, alongside Studio, Workflow, and Knowledge, and is visible only to administrators (the `MANAGE` or `PLATFORM_ADMIN` role). It gathers the organization-admin surfaces in one place — Organization Profile, Teams, Document Tags, Secrets, Intakes, Custom Modules, Subscriptions, Model Library, and Concurrency — which previously lived scattered through Studio.
  * **Organization Profile → Features.** Organization Profile now has a **Features** tab with two org-level toggles: **Strict team matching** (when on, take-next assigns only candidates matched to a reviewer's team; by default it also offers work that isn't assigned to any team when nothing matches) and **Presence tracking** (opt-in per-user presence and activity, off by default — see Platform below).

  **Workflow:**

  * **Kodexa Workflow MCP connector.** The platform now exposes its workflow surface — activities, tasks, and task groups — as an [MCP connector](/guides/mcp-connector/index) you add to claude.ai as a remote OAuth connector. It provides 19 tools spanning reads (identity, and listing/reading organizations, projects, teams, members, activities, tasks, task groups, and task statuses) and writes (assign and unassign tasks and task groups, update task status, and add or remove tasks from a group). Enable it with the `mcp.enabled` and `mcp.publicUrl` settings; claude.ai runs the OAuth flow automatically, and an `X-API-Key` fallback covers programmatic clients. Every call is scoped to the calling user's access — the connector grants no broader visibility than the REST API. Activities are read-only over MCP, and a grouped task's assignee is managed through its group.

  **Performance:**

  * **Review pages and task groups load faster.** Review tasks open faster, and the project data behind a review session is cached longer so a kiosk reviewer isn't re-fetching it on every task. The task and activity-plan list endpoints gained an opt-in `?view=summary` projection that returns a much lighter payload for high-volume listing. Task groups also prefetch the next task's document in the background, so advancing through a queue opens the next document instantly.

  **Data Forms:**

  * **Full document-viewer keyboard shortcuts on data forms.** Data forms can now drive the document viewer entirely from the keyboard — page-step and viewer-scroll alongside rotate — declared through the [declarative form-shortcuts](/guides/data-forms/shortcuts) system. New Bridge methods back this: `navigation.previousPage()` / `navigation.nextPage()` step the viewer a page at a time (clamped at the document edges), and a new `viewer.scroll(direction)` nudges the viewport up/down/left/right. `viewer.scroll` requires a new `viewer` bridge permission; the page-step methods use the existing `navigation` permission.
  * **Keyboard shortcuts fire reliably on Mac + Chrome.** Declarative form shortcuts had stopped firing for Mac users on recent Chrome; they now trigger reliably again, with no form changes required.
  * **Grid sort no longer reorders rows mid-entry.** A grid's declared `sort` is now applied once when it first loads a task, then locked — so a newly added row appends in place instead of jumping while an operator is typing. Manual column-header sorting still works, and the resulting row order is remembered per task across refresh and for other reviewers viewing the same task.
  * **Promote and Copy mark the target as edited.** Promoting or copying a value into a field now shows the same edited-value indicator as a manual edit, and click-to-source navigation still works on the copied field.
  * **Fewer redundant service-bridge lookups.** Form lookups backed by a service bridge no longer refetch when an unrelated field on the form changes — only a genuine change to the lookup's inputs re-runs it — cutting flicker and load.
  * **Large service-bridge responses no longer hang a document.** A very large service-bridge response (for example, a long list of selection options) used to stall a document on open; large responses are now handled cleanly.

  **Document:**

  * **Detached viewer behaves, and form shortcuts reach it.** Popping the document viewer out into its own window no longer freezes the page when you focus an attribute or tag in the main workspace, and declarative form shortcuts (rotate page, viewer scroll) now take effect in the popped-out window.

  **Task Groups:**

  * **Assignee picker populates again.** The assignee picker on a task group could come back empty; it now lists the organization's members and matches on email as well as name.
  * **Search matches partial terms.** Task-group search on the organization and project lists now matches partial terms anywhere in the name, instead of requiring an exact match.

  **Analytics:**

  * **Nested line-item detail reaches the data lake.** The data-lake projection previously populated only top-level data objects, so nested child groups came through empty. Nested detail now projects correctly at any depth, in the same order reviewers see it; top-level projection is unchanged. It populates on newly processed documents — reprocess if you need historical coverage.

  **Platform:**

  * **User presence & activity tracking (opt-in, off by default).** Organizations can opt into per-user presence and activity signals from **Manage → Organization Profile → Features → Presence tracking**. It is off by default, and a legal/privacy notice is shown before enabling it. When enabled, the UI reports only derived signals — whether a user is active vs. idle, tab visibility, and how long tasks take to open — sampled roughly every couple of minutes. **No raw input is ever captured**: no keystrokes, no mouse coordinates, no scroll positions. Collection is enforced on the server, so nothing is gathered for an organization that hasn't opted in.

  **Orchestrator:**

  * **Orchestrated activity hand-offs authenticate everywhere.** When one activity spawns a follow-up (from a script step or a trigger), the hand-off now authenticates correctly in every environment, so chained activities launch reliably.
</Update>

<Update label="2026-06-11" tags={["Platform", "Performance", "Document", "Data Forms", "Studio", "Formulas", "Knowledge", "Orchestrator"]}>
  ### Faster document loads, correct totals on open, and reliable saves

  This release focuses on speed and dependability across the review experience. Documents open and recalculate dramatically faster, conditional-format highlights and formula totals are now correct the moment a document opens — not only after a reviewer's first edit — and reviewer edits are reliably captured on **Approve**. It also adds a wave of new **Data Forms V2** components alongside a broad set of grid, formula, knowledge, and orchestration improvements.

  **Faster document loads and edits:**

  * **Editing large documents is dramatically faster** — Recalculating a document used to issue a separate database query for each attribute, which multiplied quickly — a single edit could cascade into thousands of queries. The same work now takes a handful (one measured edit dropped from roughly 12,800 queries to 2), so edits and their downstream recalculations stay snappy even on large documents.
  * **Documents recalculate up to \~88× faster** — Documents now refresh incrementally by default — 4.4s down to 46ms in one measured case — with an automatic fallback for unusually complex dependency graphs.
  * **Tasks open progressively** — The first data form appears as soon as its own document is ready, rather than waiting for every document on the task. Off-screen views defer presentation work — text indexing, summaries, page tags — until you open them, and redundant loading and artificial delays were removed from the open path.
  * **Switching tasks keeps sessions light** — Moving to a new task releases the documents the new task no longer needs, so long review sessions stay responsive, while a short history cache keeps back-navigation instant. Cleanup is always held off while a save, edit, or popped-out sidecar is still active.
  * **Rollup totals always match their formulas** — Rollup cards now compute through the same engine — with the same `sum()` semantics — as the formulas they summarize, and refresh together with them, so a rollup and its underlying formula can no longer disagree.
  * **Leaner edit updates** — Editing an attribute now sends just that attribute to the browser instead of its entire containing group, cutting overhead on documents with large tables.
  * **Faster, more reliable startup** — The document engine now loads entirely from Kodexa with no third-party CDN dependency at startup, and moves document data across the browser more efficiently.

  **Correct the moment a document opens:**

  * **Conditional formatting shows on open — no edit required** (ENGG-5296) — A mismatch highlight (for example, a billed total that doesn't match the summed line items) used to stay hidden until a reviewer touched the document. Highlights now render correctly on first paint, against the stored values as they are.
  * **Formula totals are correct on open** — Formula values (sums, weights, charge totals) now show their correct computed value the instant a document opens, instead of a stale zero that only corrected itself after the first edit. Documents that are already correct open clean — with no false "unsaved changes" — and any missing formula values are computed and filled in automatically.
  * **Conditional formatting updates when you delete a row** (ENGG-5265) — Deleting a line item now re-checks any total that depends on it, so a balance that moves into or out of tolerance on a delete reflects immediately.

  **Reliable saves:**

  * **Approve always captures your last edit** (ENGG-5291) — Selecting a value and immediately clicking Approve could previously miss that value if its write hadn't finished landing. Save and Approve now wait for in-flight edits to settle first — and show a clear error rather than silently continuing if an edit is stuck.
  * **Form values no longer revert after Approve** (ENGG-5269) — Editing a form and approving could take two clicks and briefly appear to revert. Edits are now applied to the document before any follow-up step runs, so the first Approve takes effect.
  * **No edits lost during a save** (ENGG-5292) — An edit made while a save is already in progress is now included in the next save instead of being dropped or needlessly re-sent.

  **Data Forms V2:**

  * **`v2:routeTimeline`** (new) — Renders a group taxon's rows as a vertical timeline of numbered stop cards for ordered lists such as a multi-stop shipment route. Stops drag-reorder (the sequence attribute is rewritten to the new position on drop), each card has an inline-editable detail panel, and per-row delete is built in. Additional props: **+ Add stop** and **AI extract stop** (single-record extraction anchored on highlighted document text, with one level of nested sub-object such as an address), a **find-in-document** button that scrolls the viewer to a stop's source span, `show: 'firstLast'` to collapse the middle of a long route behind a "…+N" toggle, `readonly` and `orientation` (vertical/horizontal) props, and an optional location-code badge. The group taxon, sequence tag, type tag, and sub-object path are all configurable.
  * **`v2:attributeCopyAction`** (new) — A scalar sibling-copy button for form layouts (distinct from the grid-cell copy components): it evaluates the source value through the formula engine and writes it to a sibling target. Like the other copy components it now writes `decimalValue`, so number-typed targets re-render immediately.
  * **`v2:attributeRowPromote`** (new) — Replaces the per-target chevron columns on candidate grids with a single **Promote to…** dropdown per row, with an optional per-target source-tag override. Promotes are idempotent and now write `decimalValue` so number targets update without tabbing away (ENGG-5217).
  * **`v2:grid` pagination, sort, and sortable custom columns** (ENGG-5267) — `v2:grid` gained opt-in `pagination` and `sort` props (reviewer grids still default to showing all rows), and custom columns can opt into sorting with a header-matched sort spec. `SELECTION` cells now close their popover on pick instead of staying open until Tab/Escape.
  * **Form Completeness Gate** (new) — A per-form primitive that lets a task action stay disabled until reviewers have actually looked at the data they should. `v2:tabs` gains `mustView` (flags unvisited tabs with an amber dot and a "N tabs to review" banner), `v2:panel` gains `mustExpand` / `mustScroll`, and outstanding workspace exceptions fold into the same list. Actions opt in with `gatedByCompleteness: true` and surface an info-popover listing what's left; existing forms and actions are unchanged.
  * **`v2:panel` polish** — New `description` subtitle prop and an `iconColor` tile palette (avatar-style tinted icon) matching the rest of the app, plus header icons on tabs and panels.
  * **Rotate-page keyboard shortcut** (ENGG-5217) — `alt+R` / `alt+shift+R` (⌥R / ⌥⇧R on Mac) rotate the current page in the document viewer right / left, wired through the [declarative form-shortcuts](/guides/data-forms/shortcuts) system. The server-side `DataForm` schema gained the `shortcuts` field, so forms declaring a `shortcuts:` block now round-trip correctly (the block was previously dropped on save).
  * **Vertical radio layout** — Attribute editors accept `radioOrientation: 'vertical'` so a `displayAsRadio` field can stack one option per line instead of wrapping across the row (default stays horizontal).

  **Document formulas & extraction:**

  * **`sumifs` / `countifs` accept single-row groups** — A formula like `sumifs({Group/Value}, {Group/Use}, true)` previously errored ("first argument must be an array") whenever the source group resolved to exactly one row, surfacing as a transient "Formula calculation failed" toast that vanished once a second row was added. Scalar range / criteria arguments are now coerced to a one-element range, so one-row and many-row groups take the same path.
  * **String cleaning patterns applied on attribute create** — Taxon `stringExtract` / `stringReplace` cleaning patterns were only applied when an attribute was updated, not when it was first created — so extraction tagging, programmatic adds, the API, and script-driven `setAttribute` all bypassed the cleaning that the same attribute would get on a later edit. The patterns now apply at create as well, and the redundant UI-side normalizer was removed.
  * **Direct copy/extract value appears immediately** — Adding a value via direct copy now patches the cache optimistically and emits the change event, so the value shows at once instead of only after tabbing out of the field (which used to trigger an expensive full-cache refresh).
  * **Preprocessor keeps rotation-corrected images** — When auto-orientation rotated a page, the rebuilt processed PDF was falling back to the original (un-rotated) source page and silently discarding the corrected image. The rebuild now embeds the rotated image bytes while preserving the viewer's "already corrected, don't CSS-rotate" signal.

  **Studio grids & activities:**

  * **Column-header clicks no longer steal focus into the first cell** (ENGG-5268) — Clicking a column header in an editable data-object grid was moving focus into row 0's first cell and opening its editor. A real user header click now suppresses that redirect while still preserving focus-from-outside and post-add-row refocus behaviour.
  * **Activities filter inputs persist across remount** (ENGG-5215) — The Activities grid's toolbar inputs (document-family filter, feature facets, quick filter, date range) and the applied filter could fall out of sync on tab switches, breadcrumb navigation, or reload — the inputs looked empty while the list stayed filtered. The toolbar state now persists and is realigned on remount.
  * **Bulk "remove by source" count stays accurate** (ENGG-5264) — The grid's per-source remove buttons could show a stale row after a bulk delete when overlapping async recounts resolved out of order; a sequence token now discards superseded runs.
  * **Grid search hydrates from the saved query** — The grid search input now repopulates from the persisted query on load, so a saved search shows its text instead of an empty box over a filtered list.

  **Knowledge:**

  * **Escaped image markdown normalized on write** — Image references pasted from Word or HTML into the rich-text editor were serialized as escaped literal text (`!\[\](attachment://…)`) and rendered as plain text instead of the image. The editor now normalizes on edit, and the API normalizes on every create/update — scoped to markdown-typed fields so intentional text escapes (e.g. `\[see codes below\]`) are preserved.
  * **Readonly-taxon panel shows the extracted value** (ENGG-5253) — The knowledge readonly-taxon panel rendered "—" instead of the extracted value when the stored dependency used the taxon's external-name chain while extraction keys attributes by taxon path — two different namespaces that never matched. The taxon picker now persists the taxon path so the reader matches directly. (Existing knowledge sets need the dependency re-selected and saved to migrate.)

  **Orchestrator:**

  * **Activity SCRIPT steps can read `inputs`** — A SCRIPT step body can now read the activity's materialized inputs via the `inputs` JS global (previously only reachable inside `BRIDGE_CALL` request templates — referencing `inputs.X` in a SCRIPT threw a ReferenceError). It always defaults to `{}`, so no `typeof` guard is needed.
  * **Script-step timeout raised to 60s; failed-step logs retained** — Long enrichment scripts on multi-document inputs were being interrupted at the old 15s limit; the `SCRIPT` step timeout is now 60s. A failed step's log pointer is also no longer discarded by the per-item rollback, so the step-logs view keeps showing the logs for a step that failed — covering `SCRIPT`, `BRIDGE_CALL`, and `AI_PROMPT` steps, which share the log path.
</Update>

<Update label="2026-05-30" tags={["Platform", "Studio", "Data Forms", "Scripting", "Formulas", "Knowledge"]}>
  ### Post-2026.4.1 patches — copy rules, source badges, multi-instance attribute paths

  The week after the 2026.4.1 cut shipped a batch of reviewer-workflow polish, new V2 data-form components for promoting candidate values into canonical slots, and the under-the-hood refactor that finally derives `DataAttribute.path` from `parent + tag` everywhere (closing out ENGG-5214). A handful of script-API and formula fixes ride along.

  **Studio reviewer workflow:**

  * **Open Task is a primary button that opens a new tab** — The activity-status dialog's "Open Task" affordance was a small text link that closed the dialog on click. It's now a primary `open-in-new` button, opens the task route in a new browser tab via `window.open`, and leaves the activity dialog mounted so reviewers keep their context.
  * **Activity dialog auto-zooms to the active step** — Opening the plan tab no longer fits the whole graph; it focuses the running step (or the last terminal step on completed plans), falling back to `fitView` only when there's no anchor. A one-shot watcher covers the race where the dialog mounts before the layout finishes.
  * **Attribute source badge: per-document-type instance numbering** — The badge now shows "Bill of Lading #1, #2, #3..." independent of how many Invoices or other classified pages interleave between them, instead of the preprocessor's global group sequence (which made every BoL in a doc read as the same number). Also fixes an off-by-one between `node.getPage()` (0-based) and the resolver's 1-based classification map that was making attributes anchored to page 2 read as page 1.
  * **Filter in ag-grid column kebab menu** — Column header menus on all ag-grid surfaces now expose ag-grid's `columnFilter` item alongside sort/pin/etc, gated on `column.isFilterAllowed()`.
  * **Page-size selector no longer steals focus into row 0** (ENGG-5252) — Clicking the grid's page-size selector previously opened the first row's first-column dropdown on top of the page-size popup. The grid's focus-redirect logic now ignores clicks landing in the pagination chrome.

  **Data Forms V2 — promotion and provenance:**

  Several new components and form-level features that work together to support the "promote a candidate value into a canonical scalar/grid slot, with provenance" workflow:

  * **`v2:attributeCopyButton.relatedCopies`** (new) — The copy button now accepts an optional `relatedCopies: [{sourceTagPath, targetTagPath}, ...]` prop. After the primary copy lands, each related pair runs through the same copy logic, so promoting a weight from a candidate-weights grid into `shipments/shipweight` can carry the matching UOM into `shipments/shipweightuom` in the same click.
  * **`v2:attributeRowDeleteButton`** + **`v2:gridDeleteBySource`** (new) — Per-row inline delete cell + a sibling toolbar component that surfaces "Remove all `<source>` rows" buttons (one per detected source document) with a confirm dialog before bulk delete. Both share the same `(document_type, group)` resolver the source badge uses.
  * **`v2:attributeSourceBadge` on `v2:grid`** (new) — `v2:grid` gained a `columns` prop that mounts arbitrary V2 components as per-row cell renderers, alongside the existing taxon-driven columns. The new `v2:attributeSourceBadge` renderer shows one colored pill per distinct `(document_type, group)` tuple of source attributes; click dispatches `workspace.focusTag` for in-viewer navigation.
  * **`v2:grid` `height` prop honored even with parent data object** — Previously `v2:grid`'s `height` was silently dropped on any grid running under a parent scope (i.e. every form grid), forcing a row-count-based auto-calculation. An explicit `height` now always wins.
  * **Form-level `copyRules` on DataFormV2** — `DataFormV2` now accepts a top-level `copyRules?: TaxonCopyRule[]`. Cards merge form-level rules with their own per-card rules (card-level wins on `sourceTaxon` conflict). Replaces having to repeat the same copy block on every source panel in forms with many source-document instances.
  * **Formula-driven `stampAttributes` on copy rules** — `CopyBehaviorOptions` gained `stampAttributes?: Record<string, string>` for stamping derived audit/provenance attributes onto the copy destination. Values can be literal strings or `${source.idString}` / `${source.parent.blnumber}` / `${source.parent.uuid}` templates. Distinct from `copyAttributes` (which clones existing source attributes with content-tag preservation), `stampAttributes` injects new derived fields with no content backing — useful for stamping `sourceDocumentRef`, `sourceBolNumber`, `sourceDocumentType` onto rows promoted into a canonical grid.

  **Scripting:**

  * **`setAttribute` routes numeric taxon types to `DecimalValue`** — `setAttribute` was only matching `DECIMAL`, so writes against `NUMBER`, `INTEGER`, `CURRENCY`, and `PERCENTAGE` taxons fell through and stored the value in `StringValue`. The form's numeric editor then read the typed slot and rendered empty even though `.value` showed the right string. The switch now covers every numeric taxon type the data model acknowledges, and routes `SELECTION` / `URL` / `EMAIL` / `PHONE` / `SECTION` / `DERIVED` into the `STRING` case explicitly.
  * **`addAttribute` auto-resolves type from the taxon** — `addAttribute`'s `TypeAtCreation` resolution now follows the same precedence as `setAttribute`: `opts.type` wins, then runtime `TaxonResolver`, then the document's cached taxonomies, then inference from the supplied typed-value field. Previously scripts calling `addAttribute({tag:"chargecode", stringValue:"DSC"})` logged "filled" but the resulting attribute couldn't bind to form `SELECTION` dropdowns, the formula evaluator, or conditional-format rules. Choice between `addAttribute` and `setAttribute` is now about find-or-create semantics, not type safety.
  * **`path` opt on `addAttribute` is ignored with a warning** — Paths have been derived from `parent + tag` since ENGG-5214; the `path` opt was silently dropped before, now `log.warn`s so callers can see they should drop it. `SPEC.go` no longer lists `path` as a valid option.
  * **Script dirty tracking across nested data objects** — `ScriptDataObject` traversal now propagates `parentDoc` so attribute changes made on a nested object correctly mark the document dirty for downstream persistence.

  **Document formulas & extraction:**

  * **Empty group refs return an empty list** (ENGG-5227) — Formulas like `sum({Accessorials/ChargeAmount})` evaluated against a parent whose child group had no instances (e.g. all accessorial rows deleted) previously returned a `"reference could not be found"` error, and the recalculator skipped the write — leaving the previously computed Sum of Line Items on screen instead of zeroing it. The path resolver now returns `[]` for the empty-group case (matching the sibling branch's existing semantics that `sum/min/max/avg` rely on), and the formula explain panel stops surfacing "Reference X/Y could not be found" for empty groups.
  * **Missing-reference errors null out the stored value** (ENGG-5227) — When `EvaluateFormula` returns a new `MissingReferenceError` (distinct from syntax/runtime errors), the recalculator now nulls the attribute's stored value fields and persists. Transient evaluation failures still preserve the previous value so they don't blank legitimate output.
  * **`DataAttribute.path` is derived everywhere** (ENGG-5214 close-out) — The `path` column has been dropped from `kddb_data_attributes`; every read now routes through `GetPath()` which composes `parent.path + "/" + tag` on the fly. Extraction, move, copy, formula reactivity, and the WASM serializer have all been migrated. External consumers reading attribute path from the JSON envelope are unaffected — the field is still emitted with the same value, just computed instead of stored. (The Go `DataAttribute.Path` field and `pathOverride` argument on `CopyDataAttribute` have been removed.)

  **Knowledge:**

  * **Knowledge feature search hits `extendedProperties` and numeric values** — The "Filter by feature" popup's `?query=` search runs against the `search_text` column, which previously only indexed slug + string-valued `Properties`. Human-readable labels stored in `ExtendedProperties.name` were never matched, and numeric scalars (e.g. `shipperCode: 1540`) were silently dropped — typing "JSP" returned zero matches even though chips render the full "JSP International - Legacy" name. `BuildSearchText` now walks both `Properties` and `ExtendedProperties` recursively, stringifying every string/number/bool scalar; a companion migration rebuilds `search_text` for every existing row so environments don't have to re-save each feature.

  **Orchestrator:**

  * **`loadTaxonomy` reads structured metadata, not stale `yaml_source`** — The script-engine adapter was reading `kdxa_taxonomies.yaml_source` and re-parsing YAML. `yaml_source` is a round-trip snapshot that drifts behind the structured `metadata` column whenever a client (`kdx sync push`, platform PUT) updates the taxonomy without rewriting the YAML. Scripts then validated taxon paths against the stale text, surfacing as `"taxon path X does not exist in taxonomy Y"` in plans that referenced recently added taxons. The adapter now reads `metadata::text` and parses the JSON via `yaml.v3` (which handles JSON as YAML 1.2).
</Update>

<Update label="2026-05-27" tags={["Release", "Platform", "Data Forms", "Studio", "Analytics", "API"]}>
  ### Release 2026.4.1

  Rollup of every customer-facing change between 2026.4 and 2026.4.1. The largest items: a new **CDC Data Lake** that mirrors every metadata change and document delta into S3, a **Page Groups** picker in the document viewer that lets reviewers jump straight to each classified section of a multi-document PDF, **declarative keyboard shortcuts** in V2 data forms, and a reshaped **Take Next** API that finally distinguishes "nothing to do" from "filtered out by team" from "lost the race." A long tail of validation, formula, and extraction fixes ride along.

  Already documented separately: [activity-plan scripts can spawn follow-up Activities](#activity-plan-scripts-can-spawn-follow-up-activities) (the 2026-05-26 entry — it's also part of this release).

  **Studio reviewer workflow:**

  * **Page Groups picker on the document viewer** — A new `file-multiple-outline` button in the spatial toolbar opens a popover listing each classified physical document on the open file (e.g. `Invoice — Pages 1–3`, `Delivery Receipt — Pages 4–6`), with a taxon-colored swatch and click-to-navigate to the start of each group. Built on top of the preprocessor's per-page classifications + group UUIDs (below) and the existing `tagMetadataMap` so the labels and colors match what the rest of the UI shows.
  * **Spatial toolbar cleanup** — The Show Advanced (`wrench`), Developer Info (`i`), and Page Groups buttons now sit before the find-text input so narrow document panels don't wrap them onto a second line.
  * **Kiosk: step-out confirm before fetching next work** — Reviewers leaving a kiosk task now get a confirm prompt before the next task is auto-claimed, preventing the accidental "I just finished but the next one already opened" race.
  * **Require a comment on task-template actions** (new) — Task-template actions can now declare `requireComment: true` (with an optional `commentPrompt` string). When the reviewer clicks the action, a shadcn dialog opens for a mandatory comment **before** the action runs; cancelling aborts the action with no partial state. The comment rides the existing `/api/batch-update` payload as `task.completedActionComment` and is persisted server-side as a `COMMENT` task activity tagged with the action's UUID so the timeline can link the comment back to the action that produced it. See [Requiring comments on actions](/guides/task-templates/requiring-comments-on-actions) for the full recipe.
  * **Take Next API: EMPTY envelope replaces 204** (ENGG-5208) — `POST /api/tasks/assign-next` previously returned a bare 204 in three distinct situations: nothing queued, filtered out by team, or claim race. The endpoint now always returns 200 with a typed envelope and a reason code (`EMPTY`, `FILTERED_OUT`, `CLAIM_LOST`) so the UI and external integrations can react appropriately. The `projectId` parameter is also now required and the team filter is enforced for platform admins (ENGG-5209). **Integrators that relied on the old 204 will need to update.**
  * **Task lock decoupled from document family lock** — A task's lock no longer takes out the entire document family. Multiple reviewers can now work different tasks against the same document concurrently when the task-status policy permits. Existing locking behaviour is preserved for status types that explicitly opt into `lockDocumentFamily`.
  * **Faceted filtering across grids** — Tasks, Task Groups, Activities, and other primary grids now support faceted filtering by document-family feature + the family itself, surfaced through a shared `KodexaGridFacetBar` component. Saved filter state survives navigation.
  * **Activities grid: file-name filter clears on task completion** — Completing a task no longer leaves a stale file-name filter on the activities grid.
  * **Team slug surfaced in grids and forms** (ENGG-5185) — Team slugs are now visible in team listings and editable in create/edit forms, matching the slug-everywhere convention used by other resources.

  **Data Forms V2:**

  * **Declarative keyboard shortcuts** (ENGG-5244, new) — V2 forms can now declare a `shortcuts:` array at the top level. Each entry binds a key combination to a named script and is registered under a per-form scope, so a form **resets all of its shortcuts every time it mounts** with no manual cleanup. See [Keyboard Shortcuts](/guides/data-forms/shortcuts) for the schema, lifecycle semantics, and worked examples.

    ```yaml theme={null}
    shortcuts:
      - key: "control+1"
        description: "Jump to Invoice"
        scriptRef: gotoInvoice
      - key: "control+t"
        description: "Focus invoice total"
        scriptRef: focusTotal
    ```

  * **Bridge navigation actions** (ENGG-5244) — `kodexa.navigation` (the bridge namespace shortcut scripts call) now has real `setPage(page, documentFamilyId?)`, `getCurrentPage(...)`, and `getPageCount(...)` methods alongside the existing `focusAttribute(...)`. Spatial methods are 1-based externally and route to the document viewer for the form's first document family by default. See [Bridge API & External Services → kodexa.navigation](/guides/data-forms/bridge-api#kodexa-navigation).

  * **Markdown editor scroll restored** — Internal scroll is back on the markdown editor, capped at 1.5× `--editor-height` so the editor no longer takes over the page on long content.

  **Document preprocessing & extraction:**

  * **Preprocessor: canonical taxon-path tagging** (ENGG-5240) — When the preprocessor is configured with a `taxonomy:` option, the LLM-returned `document_type` labels (e.g. "Bill of Lading") are translated to canonical taxon paths (`billoflading`) before pages are tagged. This makes the preprocessor's page tags match what downstream `kodexa/llm-taxonomy-model` writes, eliminating the duplicate `Invoice` + `invoice` tag instances that were appearing in reviewer-facing UI. The original LLM-returned label is still kept as a `preprocessor.document_type` feature. Existing plans without `taxonomy:` set are unchanged.
  * **Preprocessor: multi-page group on the tag** — Page tags now carry `groupUUID` (deterministic per `source_page.group`) and `value` (the sequence within the group), so consumers reading tags alone can reconstruct which pages belong to the same physical document. The existing `preprocessor.group` and `preprocessor.sequence` features are kept for backward compatibility.
  * **Spatial copy: cluster by line before sort** (ENGG-5205) — Multi-line copies from the spatial viewer were occasionally returning words in the wrong order when lines overlapped on the Y axis. The spatial sort now clusters by line first so copied text reads in natural order.
  * **Default copied attribute path** (ENGG-5214) — Copying an attribute to a different parent now defaults the new path / tag to the destination object's own path, instead of carrying the source's path forward.
  * **Formula reactivity scoped to lineage owners** (ENGG-5195) — Conditional-format and formula reactivity now routes to the lineage-scoped owner instead of fanning out across the document, which removes the perceived "everything recomputes" lag on edits in deep tag hierarchies.
  * **Conditional formats batched per owner** — `EvaluateAllConditionalFormatsBatch` is now gated on the conditional-format owner set, fixing intermittent integration-test timeouts and reducing wasted work in kodexa-ui.
  * **Reactive validation group rules + auto-derived exception path** — Group-level validation rules now re-evaluate reactively when their inputs change, and exception paths are derived automatically so authors don't have to maintain them by hand.
  * **Knowledge-feature taxon refs use canonical ExternalName chain** (ENGG-5221, ENGG-5222, ENGG-5223) — Several long-standing inconsistencies between the path used to look up a taxon and the path stored on dependency graphs / init-paths / refresh validation have been aligned to the canonical `ExternalName` chain. Symptoms that should now stop: stale validation results after a tag refresh, formulas resolving to the wrong taxon when org slugs were nested, init paths failing to find selection options after an external-name change. The WASM attribute-value bridge also now returns type-aware values (numeric, boolean, date) instead of always-string.
  * **Absolute formula refs require leading slash** (ENGG-5221) — Formula references intended as absolute (across taxonomies) must now start with `/`. Existing absolute refs that already had the leading slash are unchanged; ambiguous refs that worked accidentally before will now be treated as relative.

  **CDC Data Lake (new service):**

  Kodexa now mirrors every metadata change and document delta into an S3-backed data lake, structured for direct query by analytics tools. The lake is on by default for new deployments; existing deployments can opt in via the standard `STORAGE_LAKE_BUCKET` configuration.

  * **Envelopes** under `entities/` mirror every `kdxa_metadata_audit` write (an audit row is created for every create/update/delete of AbstractMetadata-managed resources — task templates, knowledge sets, activity plans, etc.).
  * **Activity envelopes** under `activities/` mirror every activity status transition, with rolled-up step errors and deferred publish until the originating database transaction commits (ENGG-5231) so no half-applied state leaks to the lake.
  * **Step / step-document envelopes** carry full `errorDetails` (ENGG-5235), so analytics dashboards can surface the actual exception message and stack from a failed step without re-querying the source database.
  * **Content-objects** under `content-objects/` are populated from the KDDB projection on `CONTENT_CREATED`, giving the lake the post-Apply view of every data object the orchestrator persisted.
  * **Batch context** ships task-template ref + work-session context on every batch envelope (ENGG-5202 phases 1–3), so audit / analytics consumers can correlate every change back to the user session that produced it.
  * The schema is intentionally append-only; rebuild scripts and a `CHANGELOG.md` under `kodexa-cdc-lake/` document the supported event shapes and projection logic.

  **Audit log + metadata refactor:**

  * **AbstractMetadata audit log** (ENGG-5150) — A `kdxa_metadata_audit` table now records every create/update/delete on AbstractMetadata-managed resources (task templates, knowledge sets, knowledge items, knowledge item types, knowledge feature types, knowledge features, activity plans, prompts, data forms, data definitions). Each row carries the actor, source IP, work-session ID, and a JSON snapshot of the change. Append-only enforced at the DB level.
  * **Generic slug auto-generation + uniqueness** — Any AbstractMetadata resource created without a slug now gets one auto-generated from its name, and a `UNIQUE (organization_id, slug)` index is enforced on ten audited tables. Nested org refs are resolved on every create/update so manifests can reference an org by ID, slug, or full URI.
  * **Knowledge create/update polish** — Knowledge Item creates now populate `slug` and `knowledge_set_slug` correctly. Duplicate-key responses now return a clear 409 with the conflicting field instead of a 500 (PX-10). `name` ↔ `set_name` and `type` ↔ `set_type` are mirrored in `BeforeCreate` / `BeforeUpdate` so legacy clients writing one form see consistent reads on the other.

  **Analytics datasets:**

  * **Document-family features in tasks / groups / links datasets** — The `tasks`, `task_groups`, and `task_group_links` datasets exposed via the analytics view now include each row's owning document family features (knowledge features attached to the family), so reports can group / pivot by feature without joining a separate feed.

  **Auth & user sessions:**

  * **Email extraction from non-standard claims** (ENGG-5203 phase A / A.1) — Login no longer fails for users whose ID token lacks a standard `email` claim. We now check standard OIDC claims, namespaced Auth0 claims, and finally fall back to `kdxa_users` lookup by sub.
  * **Work-session start time on /api/batch-update** (ENGG-5203 phase B) — The UI now sends `userWorkSession.startedAt` (the actual session start) instead of the dead `transactionStart` field that defaulted to the current time. Audit rows that previously showed every change as starting "now" now carry the correct session boundary.

  **kodexa-ui platform polish:**

  * **Pop-out keeps main tab as WASM owner** — Opening a document in a pop-out window no longer transfers WASM ownership; the main tab stays authoritative and the pop-out reads through.
  * **Sidecar heartbeat tightened** — Heartbeat now counts only actual ping responses, not noise from other event types, so disconnect detection is more reliable on slow networks.
</Update>

<Update label="2026-05-26" tags={["Platform", "Activities", "Scripting"]}>
  ### Activity-plan scripts can spawn follow-up Activities

  A SCRIPT step's return value can now include a `nextActivity` block that asks the platform to start another Activity Plan when the **current Activity** completes. Use this to chain related workflows — intake → extraction, classification → enrichment, validation → posting — without an external orchestrator.

  * **Return shape**: alongside `action` and the existing `features` array, scripts can now return:

    ```javascript theme={null}
    return {
      action: "approve",
      nextActivity: {
        activityPlanRef: "activity-plan://acme-finance/billing-extraction",
        inputs: { reviewedBy: org.userEmail },
        documentFamilyIds: [families[0].id],
        features: [
          { documentFamilyId: families[0].id, featureId: "fc_billing_ready" }
        ]
      }
    };
    ```

  * **Deferred spawn**: the new Activity starts when the current Activity reaches `COMPLETED`, not at the moment the script returns. Multiple SCRIPT steps in one plan may each emit their own `nextActivity` and they fan out at completion in step insertion order.

  * **Same-project only** for v1. The target plan must already be bound to the current project via `project_resources`. Cross-project spawns are rejected.

  * **Inheritance**: when `documentFamilyIds` is omitted, the spawned Activity inherits the source Activity's document families. Server-controlled `triggerMetadata` (`sourceActivityId`, `sourceStepId`, `sourceActionUuid`, `sourceProjectId`) is always merged in last so the audit trail can't be spoofed; the spawned Activity's `triggerKind` is `ACTIVITY_COMPLETED`.

  * **Soft failure**: spawn errors (missing plan, FGAC denial, input validation failure) leave the source Activity completed and record the reason in `script_result.nextActivityError`. On success, `script_result.nextActivityId` points back to the new Activity.

  * **Feature attachments**: `nextActivity.features` is applied to the named document families immediately, before the spawn fires, so the new plan's templates and scripts see them via the existing template context.

  See [Spawning a Follow-Up Activity](/guides/activity-plans/script-steps#spawning-a-follow-up-activity) for the full reference and the [Script Steps deep API reference](/guides/script-steps) for the runtime contract.
</Update>

<Update label="2026-05-13" tags={["Platform", "Activities", "Task Groups", "Knowledge", "Scripting", "API"]}>
  ### Task Groups, the New Activity experience, and knowledge in scripts

  This release rounds out the Activity-centered workflow model. Reviewers can now batch related work into Task Groups; the New Activity surface replaces the old New Task entry points; activity plans get a richer editor; and activity-plan scripts gain a first-class `knowledge` global.

  **Task Groups (new):**

  * **Bundle tasks into a single assignment unit** — Select related tasks from any Tasks tab and use **Create task group** to wrap them under one name, description, priority, status, assignee, and team. Members work the group as a unit instead of picking off individual tasks.
  * **Groups tab and kiosk Take-Next** — Each project and organization now has an always-on **Groups** tab. Reviewers can claim the next eligible group from the kiosk widget without manual selection, and the workspace opens a guided drawer with auto-advance between member tasks and a completion summary.
  * **Slide-over detail panel** — Clicking a row opens a side panel that manages status, assignee, member tasks, history, and delete in one place. The Tasks grid now shows a clickable group chip on grouped rows so you can jump straight to the panel.
  * **Permission-gated** — The Groups tab and Take-Next kiosk action appear only when the viewer's role permits them.

  **New Activity experience:**

  * **New Activity is the primary CTA** — Workflow org-home and project-home Activities tabs now lead with **New Activity** (replacing the old "New Task" button). Project Home adds a split-button so you can start an Activity or kick off a Job Run from the same control.
  * **Two-step Activity wizard** — Pick a project, pick an activity plan, then fill in title, description, priority, and documents on the same form. Document upload is inline; AI naming proposes a title from document content when enabled.
  * **Activity Plan editor** — Visual editor with tabs, schema-driven properties panel, manual layout with persisted positions, slug shown under each step's name, action-qualified edges, badges auto-generated from module refs, and per-step **Cancel** and **Reprocess** actions. Plans get a delete action consistent with task templates.
  * **Document Families on Activities** — Activities own their document families directly. The new `GET /api/activities/{id}/steps/{stepId}/document-families` endpoint returns the documents touched by a step. CREATE\_TASK steps automatically copy the activity's document families onto the task they materialize, so review surfaces always have the right context. `PATCH /api/activities/{id}/steps/{stepId}` is also available for step updates.
  * **Activity steps in the API** — `GET /api/activities/{id}` now embeds the full step list in the response.

  **Knowledge in activity-plan scripts:**

  * **`knowledge` global in script runtime** — Activity-plan SCRIPT steps and routing scripts now expose a `knowledge` object scoped to the script's permitted document families:
    * `knowledge.getFeatures(familyId)` and `knowledge.getItems(familyId)` return the raw feature and item instances on a family
    * `knowledge.featuresByType(familyId, featureTypeRef)` and `knowledge.itemsByType(familyId, itemTypeRef)` filter by type
    * When the script operates on a single family, `knowledge.features` / `knowledge.items` return that family's data directly
    * Features and items are enriched with their full type definitions and frozen, so scripts can read everything they need without separate lookups and can't accidentally mutate the source data
  * See the [Scripting — Knowledge bindings](/guides/scripting/index) guide for usage patterns.

  **Knowledge resolution by org slug:**

  * Knowledge Sets, Knowledge Items, Knowledge Item Types, Knowledge Feature Types, and Knowledge Features can now be created, updated, and referenced by `orgSlug` + `slug` (or `itemTypeRef` / `featureTypeRef`) on every create and PUT path. Manifests no longer need internal UUIDs to round-trip.
  * The resource resolver supports a new `knowledge-item://` URI scheme.
  * `/api/knowledge-features?query=` now actually searches feature name, type name, and description (previously narrowed the result set incorrectly).

  **Document family activities:**

  * **Step dots on document family cards** — Document family cards now show a row of dots indicating each activity step's state for that document. Hover for the step name and status; click an activity to open it. The same display appears in document grids via the activity cell renderer.
  * Per-document activity data is batch-fetched, so even large document lists open without per-row API churn.

  **Studio and workflow navigation:**

  * **Workstreams tab removed** — Project navigation no longer shows the legacy Workstreams tab.
  * **Resources panel** — The Studio resources panel now surfaces task templates and activity plans alongside other project metadata.
  * **Org-level activities** — Activities filter by `lifecycleState` instead of the removed `status` field; the old org-home Activities tab variant has been retired in favor of the unified grid.

  **Knowledge UI improvements:**

  * Feature palette includes a search box and caps at 30 visible items at a time, so palettes with many feature types stay usable.
  * The Applications tab shows a document family + content object inspector so you can drill into the source content behind a knowledge feature.
  * Knowledge Features get a reworked card layout and detail overview, with feature properties and `extendedProperties` exposed to AI naming templates.

  **Data form and document grid polish:**

  * **Clearing a SELECT now nulls the value** — Clearing a selection-type attribute properly clears its underlying value (not just the display string), so dependent formulas, conditional formats, and validators react correctly.
  * **"Edited Value" indicator on clears** — When you clear an AI-extracted value, the form now marks the cell as user-edited so the next extraction pass won't silently overwrite it.
  * **Better grid editing feedback** — Invalid Number cells show a focus ring while focused; grid Add no longer freezes the row gate; autocomplete dropdowns match by substring (not just prefix); SELECT popovers stay open across ag-grid cell destroy/recreate.
  * Formulas re-evaluate when the data-object cache refreshes, so derived values follow upstream edits without a manual refresh.

  **Performance:**

  * **Faster project load** — Project resources are now fetched in bulk with deduplicated module wiring, and a no-op assistant-connections endpoint has been removed from the load path.

  **Agent runtimes:**

  * Channel-scoped workspace blob store keeps chat attachments and drafts per channel.
  * Conversations continue across sessions via SessionStore continuation, so picking a chat back up doesn't reset context.
  * Task-scoped chats now see the task's document store refs.
  * Module refs can use a `{org}` placeholder so plans are portable across organizations.

  **Notes for API consumers:**

  * Activity lifecycle now lives on `Activity.lifecycleState` (`DRAFT`, `RUNNING`, `PAUSED`, `COMPLETED`, `CANCELLED`, `FAILED`). The previous `Activity.Status` field has been retired; existing data is migrated automatically. Update integrations that read the old field.
  * Activity step `kind` is now `type` across the database, API, and UI (`EXECUTION`, `SCRIPT`, `BRIDGE_CALL`, `CREATE_TASK`, `APPROVAL`, `LLM`, `AGENT`). Existing rows are migrated; new activity-plan YAML should use `type`.
  * `/api/plans` has been retired now that the UI runs entirely on `/api/activities`. The OpenAPI spec has been regenerated with 20 previously-undocumented routes added — point external integrations at `/api/activities` and refresh generated SDKs.
</Update>

<Update label="2026-05-11" tags={["Platform", "Breaking", "API"]}>
  ### Assistant connections removed

  **Breaking change.** Assistant connections and the connection-driven event router in the orchestrator have been removed across the platform.

  * `/api/assistant-connections` REST endpoints (GET, POST, PUT, DELETE) have been removed. Clients calling these endpoints will receive 404.
  * The `AssistantConnection` and `ProjectAssistantConnection` types are gone from the Python SDK and the generated TypeScript models.
  * Domain events (document family, channel, batch, content) are still emitted by producers, but the orchestrator no longer routes them to assistants via connections — those events are now drained from the SQS queue without action.
  * Activity-related events (`PLAN_CREATED`, `TASK_UPDATED`, `REPROCESS`) continue to flow through the orchestrator's plan advancer, trigger evaluator, and reprocess handler unchanged.
  * The `kdxa_assistant_connections` database table is retained but emptied by migration; nothing reads or writes it.
  * In the CLI, `kodexa-cli pull` no longer writes `assistant-connections/` directories, and `kodexa-cli apply` warns and ignores any legacy on-disk `assistant-connections/` content.
  * The Studio data-flow editor has been removed; without connections the editor had no edges to render.

  The triggers model (introduced as part of the Activity refactor) is the planned replacement for event-driven assistant invocation. The trigger evaluator is already wired into the orchestrator for `task_status_changed`; broader trigger-based routing will follow in a subsequent release.
</Update>

<Update label="2026-05-02" tags={["Platform", "Activities", "Data Definitions", "Data Forms", "CLI"]}>
  ### Activity-centered workflows, reactive validation, and richer operations tooling

  This release continues Kodexa's move to an Activity-centered model for document-heavy business processes. Activities represent the business process run. Tasks represent the human review, correction, approval, or exception work that happens inside that run.

  **New Features and Improvements:**

  * **Activities as first-class workflow runs** — Activity Plans, Activity runs, and Activity steps are now the primary model for orchestrating automated work, human review, integrations, and audit history. Activity detail APIs now include step data so user interfaces and integrations can show the run and its materialized work together.
  * **First-class Service Bridge steps** — Activity Plans can call configured Service Bridges as workflow steps. Request method, URL, body, response body, and result details are captured with the step so teams can review and troubleshoot external system calls without leaving the workflow.
  * **Activity authoring improvements** — The flow editor now supports manual layout, persisted node positions, connection labels, context menus, compact step palettes, Service Bridge nodes, schema-driven configuration, and project/intake bindings.
  * **Per-document execution visibility** — Execution details now surface at the document-family and step level, including active or failed steps, logs, execution IDs, and error details for faster operational review.
  * **Reactive Data Definition validation** — Changes to data definitions, selection options, conditional formats, validation rules, and document data now trigger scoped revalidation. Matching exceptions are created, closed, or reopened as the document moves in and out of compliance.
  * **Richer exception review in Data Forms** — Review surfaces now show more complete exception detail, filter open exceptions consistently, support override metadata and support article references, and can scope actions to specific exception paths.
  * **Knowledge snapshot review** — Knowledge Sets now include snapshot panels, feature chips, and visual diffs so teams can review knowledge changes before and after updates.
  * **CLI and GitOps improvements** — `kdx sync` now preserves project-resource links more reliably, records sync state for task templates, annotates manifest project keys with readable names, sorts legacy associations deterministically, and supports pre-package `metadata.build` hooks. A new `kdx secret` command adds organization secret management from the CLI.
  * **Document handling improvements** — Preprocessing can correct document rotation and summarize documents. The document viewer now handles processed PDFs and rotated spatial overlays more consistently.
  * **Operational observability** — Platform errors, upload failures, subscription failures, and Activity/execution status changes now emit sanitized structured events for better monitoring without exposing sensitive request data.

  **Configuration Note:**

  Module runtime configuration is now standardized on `metadata.moduleRuntimeParameters`. Update any module YAML still using `modelRuntimeParameters`.
</Update>

<Update label="2026-04-26" tags={["Platform", "Scripting", "CLI", "Breaking"]}>
  ### Script API consolidation, document-resident taxonomies, and shared script helpers

  Three related changes ship together. The first is breaking; the rest are additive and unlock smaller, more maintainable scripts.

  **1. Script API consolidation (breaking change)**

  The JavaScript API for intake scripts, planner script steps, taxonomy event subscriptions (browser and Python contexts), and module scripts has been modernized onto a single canonical surface. **Scripts written against the legacy API need updating.**

  * **Method names are now camelCase.** `currentObject.GetFirstAttributeValue("foo")` → `currentObject.getFirstAttributeValue("foo")`. Same rule for every method on `currentObject`, `document`, attributes, and content nodes. Scripts using PascalCase fail with `TypeError: Object has no member 'GetFirstAttributeValue'`.
  * **`bridge.data.*` is removed.** `bridge.data.setAttribute(currentObject.GetID(), name, value)` collapses to `currentObject.setAttribute(name, value)`. `bridge.data.getAttribute(...)` collapses to `currentObject.getFirstAttributeValue(name)`. Calling the removed surface throws `bridge.data is undefined`.
  * **`log()` is now structured.** Replace `log("info", "msg: " + x)` with `log.info("msg:", x)`. `log.warn`, `log.error`, and `log.debug` follow the same variadic shape (args joined with spaces, like `console.log`). Calling `log()` positionally throws `log is not a function`.
  * **`getType()` on content nodes is renamed to `getNodeType()`.** Scripts iterating selector results and reading the node type need a one-word find/replace.
  * **`serviceBridge.list()` is removed.** Discovery now lives in the platform admin surface; scripts reference bridges by known `"orgSlug/bridgeSlug"` refs.

  New helpers that reduce boilerplate:

  * **`doc.getOrCreate(path)` / `obj.getOrCreateChild(path)`** — find-or-create idempotent. Replaces the `findFirst → if null create` pattern.
  * **`obj.setAttribute(name, value)`** — find-or-create on an attribute and write a typed value in one call.
  * **`obj.payload({ key: "attrName", ... })`** — extract a JS object suitable for `serviceBridge.call` payloads. Missing attributes default to `""`.
  * **`taxon.optionLabel("taxonName", value)`** — look up the human label for a selection-option value. Replaces hardcoded label maps in event scripts.
  * **Default `path` and `ownerUri`** — write methods (`addAttribute`, `copyAttributeFrom`, etc.) derive `path` from `parent.path + "/" + tag` and `ownerUri` from the runtime's script context. Specify only when overriding.

  Migration: every change is a mechanical find/replace. The full reference is in the [Scripting guide](/guides/scripting/index). Tenants with custom scripts stored in the platform (intake scripts, planner scripts, taxonomy event subscriptions edited via the UI) should update those scripts before upgrading. The browser WASM bundle is bumped to API version 2; the existing reload prompt detects mismatched bundles and asks users to reload.

  **2. Document-resident taxonomy resolution**

  `setAttribute(name, value)` now resolves the attribute's type from the document's cached taxonomies — scripts no longer need to declare `type: "SELECTION"` (or any other type) on writes. The resolution chain is: VM-supplied resolver → document's cached taxonomies (loaded by extraction) → fallback inferred from the JS value's runtime type.

  ```javascript theme={null}
  // Before — explicit type declaration needed
  shipment.addAttribute({ tag: "shippercode", value: "142600", stringValue: "142600", type: "SELECTION" });

  // After — type resolved from the document's taxonomies
  shipment.setAttribute("shippercode", "142600");
  ```

  Scripts can also extend the document's in-scope taxonomies at runtime:

  ```javascript theme={null}
  document.addTaxonomy(myTaxonomy);  // subsequent setAttribute calls see it
  ```

  **3. Script sidecars on task templates**

  Task-template SCRIPT items can declare a list of module refs whose JS scripts are pre-loaded into the runtime before the main script body runs. This lets multiple templates share helper functions instead of inlining the same code in every block.

  ```yaml theme={null}
  - type: SCRIPT
    name: Initial Enrichment
    scriptSidecars:
    - acme-finance/invoice-helpers
    script: |
      // copyNewAttributes is provided by the sidecar — no inline definition needed
      copyNewAttributes(target, source, [{src: "amount"}, {src: "date"}]);
  ```

  Refs use the standard `"orgSlug/moduleSlug"` form. Module declarations (loaded as functions/vars) land on the global scope and behave like any other helper in the main script.

  **4. CLI: `metadata.scriptPath` for module YAML**

  Module YAML can now reference an external `.js` file via `metadata.scriptPath`; `kdx sync push` reads the file and inlines it into `metadata.script` at deploy time:

  ```yaml theme={null}
  # models/invoice-helpers.yml
  orgSlug: acme-finance
  slug: invoice-helpers
  type: store
  storeType: MODEL
  metadata:
    scriptLanguage: javascript
    scriptPath: ../scripts/invoice-helpers.js   # editor-friendly source of truth
  ```

  Inline-script-only modules (the sidecar shape) don't need a `contents:` block — `kdx` skips the implementation zip-and-upload path entirely and logs `📜 Module {slug} is inline-script only — skipping implementation upload`.

  Existing modules with inline `metadata.script` or a populated `contents:` block are unaffected.
</Update>

<Update label="2026-04-14" tags={["Platform", "CLI", "SDK"]}>
  ### Platform Updates — Mid April 2026

  **New Features:**

  * **Markdown Image Paste** — Markdown editors now support Cmd/Ctrl+V image pasting and drag-and-drop. When editing knowledge items, pasted images are automatically uploaded as knowledge set attachments and referenced via portable `attachment://` URLs. In standalone contexts, images are base64-encoded inline.
  * **Selection Option Formulas** — Data definitions now support formula-driven selection options with a formula mode toggle and extended fields, enabling dynamic dropdown values computed from other attribute values in the document.
  * **Exception Override** — Data forms now support overriding validation exceptions directly from the workspace with WASM persistence and a form-scoped exception details panel.
  * **Tab Key Grid Navigation** — Tab key now navigates between input fields in grid cells for faster data entry.
  * **Filterable Knowledge Tables** — Markdown tables rendered in knowledge sections now include a search bar for filtering rows.
  * **Service Bridge Status Override** — `postReplyScript` can now override the HTTP response status code returned by a service bridge endpoint.
  * **Intake and Label URI Schemes** — The API resolver now supports `intake://` and `label://` URI schemes for resource resolution.

  **CLI Improvements:**

  * **YAML Round-Trip Preservation** — `kdx sync pull` now preserves YAML comments and formatting using a new `yamlpatch` engine. Pushed resources include the original YAML source for lossless round-trips.
  * **Smart Discover Merge** — `kdx sync pull --discover` intelligently merges newly discovered resources into existing YAML files, preserving comments and manual edits.
  * **Attachment Download** — `kdx sync pull` now downloads knowledge set attachments alongside metadata.
  * **Discover Directory Flag** — New `--discover-dir` flag sets the `metadata_dir` in the generated manifest during discovery.
  * **Conflict Detection** — Sync state tracking detects when remote resources have changed since the last pull, with a `--force` flag to override conflicts.
  * **Cross-Org Push** — `kdx sync push` rewrites organization slug references in YAML values when pushing to a different organization.
  * **Dependency-Aware Push** — Resources are pushed in dependency order to avoid reference errors during deployment.
  * **Parallel Pull/Push** — Sync operations now run in parallel for faster execution.
  * **Legacy Server Compatibility** — Improved compatibility with older Kodexa servers including paginated API responses, case-insensitive slug matching, and fallback resource fetching.

  **Platform Improvements:**

  * **Optimistic Locking** — Tasks, document families, and batch updates now use change-sequence-based optimistic locking to prevent concurrent modification conflicts.
  * **Service Bridge Observability** — Service bridge proxy calls now emit Datadog events with request and response body details.
  * **Selection Formula Scoping** — Selection formula evaluation is now scoped to ancestor data objects for more predictable results.
  * **Auto-Select Single Option** — Dropdown fields with a single available option are automatically selected when the field is empty.
  * **Attribute Editor Consistency** — Attribute editors now emit updates on blur rather than on every keystroke, reducing unnecessary saves.
  * **Readonly Field Styling** — Readonly form fields are now visually distinguished with a border and muted background.
  * **WASM Binary Size** — The WASM binary has been reduced by 20.8% by removing unused expression engine dependencies.
  * **Detached Sidecar Toolbar** — The detached sidecar window now includes the full document toolbar and page navigation.

  **Bug Fixes:**

  * Fixed service bridge calls not re-firing when dependency values changed.
  * Fixed selection options formula toggle not responding to clicks.
  * Fixed missing formula attribute references causing errors instead of resolving to nil.
  * Fixed document store table view checkbox selection and row click behavior.
  * Fixed chat session loading failing when reopening an existing conversation.
  * Fixed LLM JSON response preprocessing to handle truncated or malformed responses.
  * Fixed task locking and auto-lock behavior in plan advancement.
  * Fixed form freeze, dropdown UX glitches, and validation timing issues in data forms.
  * Fixed inline grid editing focus loss when attribute data updates arrived.
  * Fixed extracted selection values being accepted even when not in the dropdown options list.
  * Fixed GoJA script runtime not persisting `AddChild`, `SetTaxonomy`, `SetPath`, and attribute mutations to the document.
</Update>

<Update label="2026-04-05" tags={["Platform", "CLI", "SDK"]}>
  ### Platform Updates — Early April 2026

  **Improvements:**

  * **Scoped Document Reprocessing** — `POST /api/document-families/{id}/reprocess` now accepts an optional `assistantIds` request body. When omitted, the platform auto-detects prior assistant contributions and reprocesses the family asynchronously.
  * **Document Family Feature Filters** — Document families can now be filtered through attached knowledge-feature relationships such as `features.id=='...'` and `features.slug=='...'`.
  * **Module Package Selection** — Python runtimes now honor `metadata.moduleRuntimeParameters.module` when a module archive contains multiple packages, ensuring the intended package is imported before execution.
  * **Completion Event Chaining** — Applying an execution's `completeLabel` now emits a follow-up `CONTENT_CREATED` event so downstream subscriptions can react to the finalized content.
</Update>

<Update label="2026-03-24" tags={["Platform"]}>
  ### Platform Updates — Late March 2026

  **New Features:**

  * **Intake API Tokens** — Intakes now support scoped API tokens for machine-to-machine authentication. Create tokens on the new API Tokens tab; each token authenticates directly against a specific intake endpoint without requiring user credentials. Tokens are hashed at rest with SHA-256.
  * **Detachable Sidecar** — Pop out the sidecar document viewer into a separate browser window using the detach button. The inline sidecar collapses while the external window is open and automatically restores when the external tab is closed.
  * **Smart Grid Filters** — All grid views (tasks, projects, document families, etc.) now include a unified search bar with recent query history, structured filter mode with metadata-aware autocomplete, and AI-powered natural language filter generation.
  * **Script Step Log Viewer** — Script step logs are now captured in CloudWatch and viewable directly from Activity step details. Each script execution automatically records start/end entries and all `log()` calls. See [Script Steps](/guides/script-steps/index#viewing-script-logs).
  * **Knowledge Expression Trees** — Knowledge sets now support expression-based feature matching using AND, OR, and NOT operators, replacing the previous clause-based system. This enables more flexible conditional logic for knowledge assessment. See [Knowledge System](/concepts/knowledge_system#expression-based-matching).
  * **UNO Document Converter** — New module runtime for converting Office documents (Word, Excel, PowerPoint) via LibreOffice/UNO. Available as `kodexa/uno-runtime`. See [Module Runtimes](/concepts/module_runtimes#available-runtimes).
  * **LLM Model Manager** — New Python SDK `ModelManager` class provides unified access to all LLM models through the Kodexa AI Gateway. Supports text completion, function calling, streaming, thinking mode, and multimodal input. Replaces direct provider SDKs with a single gateway client. See [LLM & Model Manager](/sdk/python/llm).
  * **AI Grid Extraction** — Data forms using `v2:grid` layout now support AI-powered grid extraction with word-level node tagging for more accurate table data capture. See [Data Forms Extraction](/guides/data-forms/extraction#ai-extraction-on-grids).
  * **Direct Extract** — New `allowDirectExtract` option on data form attribute editors lets users copy values directly from the document text without an AI call. See [Data Forms Extraction](/guides/data-forms/extraction#direct-extract).

  **Improvements:**

  * **CLI Re-Authentication** — The CLI now automatically prompts for re-authentication when it encounters a 401 response, instead of failing. See [CLI Authentication](/guides/kdx-cli/authentication#automatic-re-authentication).
  * **Access Token Security** — API access tokens are now hashed at rest using SHA-256. The profile access tokens UI has been redesigned with confirmation dialogs before deletion and tokens scoped to the current user.
  * **Module Ref Rename** — The bridge script parameters `model_store` and `model_options` have been renamed to `module_ref` and `module_options` for consistency. See [Module Runtimes](/concepts/module_runtimes#magic-parameter-injection).
  * **Datadog Observability** — New instrumentation events for agent runtimes, agent instances, and LLM calls in the AI Gateway for Datadog monitoring.
  * **Execution Cancel** — The execution cancel button in the UI is now wired to the API endpoint.
  * **Taxonomy → Data Definition** — The UI label "Taxonomy" has been renamed to "Data Definition" across resource badges and labels for consistency with the platform terminology.
</Update>

<Update label="2026-03-16" tags={["Platform"]}>
  ### Platform Updates — March 2026

  **New Features:**

  * **Agentic Assistants** — A new assistant role that delegates processing to AI agents. Configure an agent runtime, module references, and a natural language prompt to let the agent autonomously decide how to process documents. See [Assistants](/concepts/assistants#agentic-assistant).
  * **Intake Enhancements** — Intakes now support JavaScript scripting for file validation and metadata enrichment, task template integration for auto-creating tasks on upload, multi-file uploads, knowledge feature assignment, and processing metadata.
  * **Activity Workflow Enhancements** — New SCRIPT and AGENT step types. SCRIPT steps run inline JavaScript for conditional routing. AGENT steps spawn AI agents in workflow execution. Action-qualified dependencies enable conditional branching (e.g., proceed only on "Approve"). Activity runs display as interactive DAG flow visualizations with automatic deadlock detection.
  * **AI Task Naming** — Task templates can configure LLM-powered naming so tasks receive descriptive titles based on document content.
  * **Notification Sounds** — Toggle audio notifications for error/warning toasts and new channel messages from your profile preferences.
  * **Document Native Download** — New `GET /api/document-families/{id}/native` endpoint returns the original uploaded file. See [Download original native file](/api-reference/documentfamilies/get-document-families-id-native).
  * **Execution Log Viewer** — Restyled with syntax highlighting, auto-scroll, copy and download buttons, and dark theme enforcement.
  * **Progress Toasts** — Consolidated progress notifications for document uploads and batch reprocessing operations.

  **Improvements:**

  * **Secrets API** — Secrets are now managed through organization-scoped endpoints (`/api/organizations/{orgId}/secrets`) with secure encrypted storage.
  * **Resource Resolver** — Three new project-scoped schemes: `task-status`, `task-template`, and `assistant`. See [Components and Structure](/concepts/components_and_structure#project-scoped-resources).
  * **CLI Resource Resolution** — `kdx apply` now resolves project-scoped resources (task statuses, task templates, assistants) with the `scheme://org/project/slug` URI format.
  * **AI Gateway** — Extended model metadata with pricing, description, and classification fields. Cloud models now proxy through the AI gateway.
  * **Store Reprocessing** — Reprocess documents within a store with assistant selection and filtering.
  * **Document Groups** — New `hardMaxPages` field to enforce a hard page count limit at upload time.
</Update>

<Update label="2026-03-07" tags={["CLI", "Pre-release"]}>
  ### KDX CLI v2026.3.0 (Pre-release)

  <Note>
    This version is currently available as a pre-release. Install via `brew install kodexa-ai/tap/kdx-dev` to try it out.
  </Note>

  Bug fixes, deployment reliability, and E2E test coverage:

  **Bug Fixes:**

  * **Apply Ordering Fix**: Fixed an issue where `kdx apply` for modules would overwrite metadata changes. Implementation uploads now happen before metadata PUT, ensuring inference options and other metadata updates are preserved.
  * **OpenAPI Resource Discovery**: Manual resource definitions now properly override OpenAPI-discovered resources, preventing bogus CRUD paths from being generated for document-stores and other hyphenated resource types.
  * **Hyphenated Resource Types**: Fixed resource discovery for `data-store`, `data-definition`, and `document-store` by normalizing hyphens and underscores in resource lookup.
  * **Deploy Failure Reporting**: Deployment failures are now properly surfaced with error counts and non-zero exit codes, instead of silently reporting success.
  * **ID Stripping on Create**: The CLI now strips `id` and `_id` fields from CREATE payloads since IDs are server-generated, preventing conflicts during resource creation.

  **Improvements:**

  * **E2E Test Suite**: Added comprehensive end-to-end tests covering document family reprocessing, knowledge-set CRUD and resolution, module upload/download, and CLI-to-API integration.
  * **Filter Syntax**: Updated to SpringFilter DSL syntax for resource filtering, with syntax reference added to `kdx get --help`.
  * **Sort Parameters**: Standardized sort parameter format across all commands.

  **Impact:**

  * Module metadata (inference options, configuration) is now reliably preserved during `kdx apply` operations
  * `kdx sync deploy` now correctly reports failures and returns non-zero exit codes for CI/CD pipelines
  * Resource operations for hyphenated types (data-store, document-store, data-definition) work reliably

  **Breaking Changes:** None - fully backward compatible with v2026.2.1

  Available as pre-release via Homebrew: `brew install kodexa-ai/tap/kdx-dev`
</Update>

<Update label="2026-02-09" tags={["CLI"]}>
  ### KDX CLI v2026.2.1

  Bug fixes for project-scoped resource syncing and OpenAPI spec parsing:

  **Bug Fixes:**

  * **Project-Scoped Resource Sync**: Fixed an issue where syncing project-scoped resources (e.g., task templates) would fail with "TaskTemplate must have a project" errors. The PUT payload now correctly includes the project reference for project-scoped resources, mirroring the existing pattern for organization-scoped resources.
  * **OpenAPI Schema Parsing**: The CLI now gracefully handles missing `$ref` schema references in the server's OpenAPI specification. When broken references are encountered (e.g., a missing `ValidationFailedResponse`), the CLI patches in empty object stubs and retries parsing instead of failing.

  **Impact:**

  * Task template and other project-scoped resource syncing now works correctly with `kdx sync push` and `kdx sync deploy`
  * CLI operations no longer fail when the platform's OpenAPI spec contains missing schema definitions

  **Breaking Changes:** None - fully backward compatible with v2026.2.0

  Available via Homebrew: `brew upgrade kdx`
</Update>

<Update label="2026-02-06" tags={["CLI"]}>
  ### KDX CLI v2026.2.0

  Document Command Overhaul, Knowledge Management & Module Downloads:

  **Version Scheme Change:**
  The CLI version now aligns with the platform release cycle (2026.2.x), replacing the previous 0.x/8.x numbering.

  **Document Command Rewrite:**
  All `kdx document` commands have been rewritten to use the native kodexa-document Go library via a new DocumentAdapter, providing significantly improved performance and richer output including node IDs and type information.

  **New Document Subcommands:**

  * **`kdx document stats`** - Document statistics summary
  * **`kdx document schema`** - Display document schema
  * **`kdx document tags`** - List and inspect tags
  * **`kdx document features`** - List and inspect features
  * **`kdx document node`** - Inspect individual nodes
  * **`kdx document text`** - Extract text content
  * **`kdx document page`** - Page-level operations
  * **`kdx document find`** - Multi-criteria search across nodes
  * **`kdx document spatial find`** - Spatial search by coordinates
  * **`kdx document spatial bbox`** - Bounding box queries
  * **`kdx document data objects`** - List data objects
  * **`kdx document data attributes`** - List data attributes
  * **`kdx document data exceptions`** - List data exceptions
  * **`kdx document audit`** - View audit trail entries

  **Knowledge Management:**

  * **`kdx knowledge attach`** - Attach files to knowledge sets
  * **`kdx knowledge download`** - Download knowledge set items
  * **`attachmentPath` support** in `kdx sync` for knowledge items

  **Module Downloads:**

  * **`kdx get module <slug> --download`** - Download module implementation packages directly

  **Sync Improvements:**

  * **Project-scoped resources** with auto-pull for task templates
  * Graceful handling of missing files in pull operations

  ```bash theme={null}
  # Document inspection with enriched output
  kdx document stats invoice.kddb
  kdx document find invoice.kddb --type paragraph --content "total"
  kdx document spatial bbox invoice.kddb --page 1 --x1 0 --y1 0 --x2 500 --y2 200
  kdx document audit invoice.kddb

  # Knowledge management
  kdx knowledge attach my-org/knowledge-set:1.0.0 ./data.csv
  kdx knowledge download my-org/knowledge-set:1.0.0 item-id -o output.csv

  # Module download
  kdx get module my-org/my-model:1.0.0 --download
  ```

  **Breaking Changes:** None - fully backward compatible with v8.0.0

  Available via Homebrew: `brew upgrade kdx`
</Update>

<Update label="2026-01-28" tags={["CLI"]}>
  ### KDX CLI v0.6.0

  Content Object Access & Extended Store Commands:

  **New Features:**

  * **Document Family Content Commands**: New `kdx document-family content` subcommand for direct access to content objects (kddb files) within document families:
    * **`kdx document-family content list`** - List all content objects with IDs, timestamps, and labels
    * **`kdx document-family content download`** - Download kddb files directly, bypassing DFM export timeouts
    * **`--latest` flag** - Automatically select the most recent content object
    * **`--output` flag** - Specify custom output filename

  * **Store Upload & Watch Commands**: New commands for document upload and processing workflows:
    * **`kdx store upload`** - Upload files (PDF, images, documents) to document stores
    * **`kdx store watch`** - Monitor document processing progress with real-time status updates
    * **`--label` flag** - Wait for specific processing labels (PREPARED, FIRST-PASS, LABELED, PROCESSED)
    * **`--timeout` flag** - Configure wait timeout for long-running processing

  **Improvements:**

  * **Project Create Organization Lookup**: Fixed organization resolution when creating projects from templates
  * **Dynamic API Flags**: Improved handling of dynamic flags for `kdx run` operations
  * **Document Family Data Export**: Fixed data export to always use latest content object

  **Usage Examples:**

  ```bash theme={null}
  # List content objects in a document family
  kdx document-family content list 70b894f5-8d32-4584-b780-89f89210e078

  # Download the latest kddb file
  kdx document-family content download 70b894f5-8d32-4584-b780-89f89210e078 --latest -o document.kddb

  # Upload and monitor processing
  kdx store upload satori/project-processing:1.0.0 ./report.pdf
  kdx store watch abc123 --label PROCESSED --timeout 600
  ```

  **Documentation:**

  * [Document Family Commands](/guides/kdx-cli/document-family-commands)
  * [Store Commands](/guides/kdx-cli/store-commands)

  **Breaking Changes:** None - fully backward compatible with v0.5.x

  Available via Homebrew: `brew upgrade kdx`
</Update>

<Update label="2026-01-07" tags={["CLI", "SDK", "Documentation"]}>
  ### v8 Documentation Preview

  Pre-release documentation for upcoming v8 CLI and SDK releases:

  **CLI v8 - Document Commands (Preview):**

  New `kdx document` command suite for working with local KDDB files without requiring a platform connection:

  * **`kdx document info`** - Display document summary with metadata and statistics
  * **`kdx document print`** - Pretty print document structure as an ASCII tree with depth limiting and feature display
  * **`kdx document select`** - Query nodes by type using selector syntax
  * **`kdx document natives list/extract`** - List and extract embedded files (PDFs, images, etc.)
  * **`kdx document external list/get/set/delete`** - Manage key-value external data store
  * **`kdx document metadata get/set`** - View and modify document metadata

  ```bash theme={null}
  # Quick document inspection
  kdx document info invoice.kddb
  kdx document print invoice.kddb --depth 3
  kdx document select invoice.kddb "//paragraph"

  # Extract embedded PDF
  kdx document natives extract invoice.kddb original.pdf -o extracted.pdf
  ```

  **SDK Documentation Updates:**

  * **Native Documents**: Store and retrieve binary files within KDDB documents
  * **External Data**: Flexible key-value store for custom data and processing results
  * **Metadata**: Document properties including UUID, version, and custom fields
  * **Content Nodes**: Hierarchical document structure with types and content
  * **Selectors**: XPath-like query syntax for finding nodes

  **Documentation Links:**

  * [CLI Document Commands](/guides/kdx-cli/document/overview)
  * [SDK Native Documents](/sdk/native-documents)
  * [SDK External Data](/sdk/external-data)
  * [SDK Metadata](/sdk/metadata)

  <Note>
    These features are in preview and will be included in the upcoming v8 release of the KDX CLI and Kodexa Document SDKs.
  </Note>
</Update>

<Update label="2026-01-03" tags={["SDK", "Documentation"]}>
  ### Kodexa Document SDK v8.0.0

  New SDK Documentation & Version 8 Libraries:

  **New Documentation:**

  * **SDK Documentation Tab**: Added a dedicated SDK section to the developer portal with comprehensive guides for Python and TypeScript
  * **Python Getting Started**: Complete guide covering installation, document creation, node manipulation, selectors, features, tags, and saving
  * **TypeScript Getting Started**: Full guide including WASM initialization, async patterns, memory management, and browser/Node.js setup

  **Python SDK - kodexa-document v8.0.0:**

  * High-performance document processing via Go backend with CFFI bindings
  * \~100x faster in-memory mode for processing pipelines
  * Full support for KDDB format, hierarchical nodes, features, tags, and XPath-like selectors
  * Context manager support for automatic resource cleanup

  ```bash theme={null}
  pip install kodexa-document
  ```

  **TypeScript SDK - @kodexa-ai/document-wasm-ts v8.0.0:**

  * WebAssembly-powered SDK for Node.js and modern browsers
  * \~5x faster than pure JavaScript implementations
  * Full async API with TypeScript type safety
  * Works with both file-based and in-browser SQLite

  ```bash theme={null}
  npm install @kodexa-ai/document-wasm-ts
  ```

  **Key Features (Both SDKs):**

  * Create, load, and save KDDB documents
  * Hierarchical ContentNode tree structure
  * XPath-like selector queries
  * Features (key-value metadata) and Tags (annotations with confidence)
  * JSON and binary export formats

  **Links:**

  * [Python SDK Documentation](/sdk/python)
  * [TypeScript SDK Documentation](/sdk/typescript)
  * [PyPI - kodexa-document](https://pypi.org/project/kodexa-document)
  * [npm - @kodexa-ai/document-wasm-ts](https://www.npmjs.com/package/@kodexa-ai/document-wasm-ts)
</Update>

<Update label="2025-12-04" tags={["CLI"]}>
  ### KDX CLI v0.5.2

  Stability & User Experience Improvements:

  **Bug Fixes:**

  * **Increased Client Timeout**: Extended HTTP client timeout from 60 seconds to 10 minutes (600s) to prevent premature failures during long-running deployment operations, particularly beneficial for large manifest deployments, multiple resource/module deployments, and deployments to slow or distant environments
  * **Improved Branch Mapping Error Handling**: Changed behavior when no branch mapping is found - now displays informational message and exits gracefully instead of returning error, providing better UX when working on unmapped branches

  **Improvements:**

  * **Better CI/CD Integration**: Non-disruptive behavior when working on unmapped branches doesn't fail pipelines unnecessarily
  * **Clearer User Feedback**: Informational messages clearly distinguish between configuration issues and actual errors
  * **Enhanced Reliability**: Deployments that previously failed due to timeout will now complete successfully

  **Impact:**

  * Prevents deployment timeouts for operations with large manifests or multiple resources
  * Better user experience when working with selective branch mapping configurations
  * More reliable long-running deployment operations

  **Breaking Changes:** None - fully backward compatible with v0.5.0 and v0.5.1

  **Download Links:**

  * [macOS (Intel)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.5.2/kdx_0.5.2_darwin_x86_64.tar.gz)
  * [macOS (Apple Silicon)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.5.2/kdx_0.5.2_darwin_arm64.tar.gz)
  * [Linux (x86\_64)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.5.2/kdx_0.5.2_linux_x86_64.tar.gz)
  * [Linux (ARM64)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.5.2/kdx_0.5.2_linux_arm64.tar.gz)
  * [Windows (x86\_64)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.5.2/kdx_0.5.2_windows_x86_64.zip)

  Available in [kdx-cli v0.5.2](https://github.com/kodexa-ai/kdx-cli-releases/releases/tag/v0.5.2)
</Update>

<Update label="2025-12-03" tags={["GitHub Action"]}>
  ### Kodexa Sync Action v2.2.0

  Built-in Slack Notifications & GitHub Job Summary:

  **New Features:**

  * **Slack Notifications**: Send rich deployment summaries to Slack automatically with `slack-channel-id` and `slack-token` inputs
  * **GitHub Job Summary**: Add deployment summary to workflow run with `annotate-summary: true`
  * **Renamed Input**: `workers` → `threads` to match kdx-cli flag naming

  **Slack Message Includes:**

  * 🚀 Deployment status (or 🔍 for dry runs)
  * Repository and branch information
  * Resource counts (created, updated, unchanged)
  * Direct link to the GitHub Actions run

  **Usage Example:**

  ```yaml theme={null}
  - uses: kodexa-ai/kdx-sync-action@v2
    with:
      threads: 8
      annotate-summary: true
      slack-channel-id: $\{{ secrets.SLACK_CHANNEL_ID }}
      slack-token: $\{{ secrets.SLACK_BOT_TOKEN }}
    env:
      KODEXA_PROD_API_KEY: $\{{ secrets.KODEXA_PROD_API_KEY }}
  ```

  Available in [kdx-sync-action v2.2.0](https://github.com/kodexa-ai/kdx-sync-action/releases/tag/v2.2.0)
</Update>

<Update label="2025-12-03" tags={["CLI"]}>
  ### KDX CLI v0.5.0

  Tag-Based Deployments & Enhanced GitOps:

  **Major Features:**

  * **Tag-Based Deployment Mappings**: Deploy using git tags in addition to branches, enabling release-driven workflows with `tag_mappings` configuration supporting semantic versions, release candidates, and preview tags
  * **Manual Deployment Overrides**: New `--branch` and `--tag` flags provide explicit control over deployment routing without requiring git operations, perfect for CI/CD, testing, and rollback scenarios
  * **JSON Deployment Reports**: Generate structured JSON reports of deployment actions with `--json-report <path>` for CI/CD integration
  * **Parallel Resource Deployment**: New `--threads <n>` flag for configuring parallel threads during resource deployment - significantly faster for large deployments
  * **Resource Filtering**: Filter resources during deployment with `--filter <pattern>` for selective deployments

  **Improvements:**

  * **Enhanced Error Messages**: Clear, actionable error messages with hints when mappings are not found
  * **Better User Feedback**: Deployment mode indicators showing whether using branch detection, tag detection, or manual override
  * **Improved Mapping Resolution**: Support for multiple overlapping mappings, enabling sophisticated multi-environment deployment strategies

  **Usage Examples:**

  ```bash theme={null}
  # Deploy with JSON report
  kdx sync deploy --json-report ./deploy-report.json

  # Deploy with parallel threads (8)
  kdx sync deploy --threads 8

  # Deploy specific tag mapping
  kdx sync deploy --tag v1.0.0

  # Filter resources during deployment
  kdx sync deploy --filter "invoice-*"
  ```

  **Configuration - Tag Mappings:**

  ```yaml theme={null}
  branch_mappings:
    - pattern: "main"
      target: production
      environment: prod

  tag_mappings:
    - pattern: "v*"
      target: production
      environment: prod
    - pattern: "rc-*"
      target: staging
      environment: staging
  ```

  **Download Links:**

  * [macOS (Intel)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.5.0/kdx_0.5.0_darwin_x86_64.tar.gz)
  * [macOS (Apple Silicon)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.5.0/kdx_0.5.0_darwin_arm64.tar.gz)
  * [Linux (x86\_64)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.5.0/kdx_0.5.0_linux_x86_64.tar.gz)
  * [Linux (ARM64)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.5.0/kdx_0.5.0_linux_arm64.tar.gz)
  * [Windows (x86\_64)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.5.0/kdx_0.5.0_windows_x86_64.zip)

  Available in [kdx-cli v0.5.0](https://github.com/kodexa-ai/kdx-cli-releases/releases/tag/v0.5.0)
</Update>

<Update label="2025-12-02" tags={["CLI"]}>
  ### KDX CLI v0.4.1

  Enhanced Debugging, Error Handling & Sync Improvements:

  **New Features:**

  * **Enhanced Client Debugging**: Added detailed API request/response logging when debug mode is enabled, providing comprehensive information for troubleshooting
  * **Improved Module Syncing**: Updated module syncing to build and display full slugs with organization prefixes, improving clarity in logs and progress reporting
  * **Enhanced Deployment Output**: Deployment command now includes the environment URL in planned deployment messages, providing clearer context for users
  * **Alternate Extension Support**: Added support for both `.yaml` and `.yml` extensions when reading resource files, with improved error hints showing all attempted file paths

  **Improvements:**

  * **Better Error Messages**: Error messages now reference full slugs with organization prefixes, making it easier to identify and debug issues
  * **Improved Error Handling**: Enhanced deployment error handling to capture and report errors without terminating the process immediately
  * **Robust Payload Handling**: New utility function to safely extract string values from interface types
  * **Debug Mode Formatting**: Conditionally display full response bodies based on debug mode for cleaner output in normal operation

  **Code Quality:**

  * Fixed gofmt formatting in `resource_types_test.go`

  **Download Links:**

  * [macOS (Intel)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.4.1/kdx_0.4.1_darwin_x86_64.tar.gz)
  * [macOS (Apple Silicon)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.4.1/kdx_0.4.1_darwin_arm64.tar.gz)
  * [Linux (x86\_64)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.4.1/kdx_0.4.1_linux_x86_64.tar.gz)
  * [Linux (ARM64)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.4.1/kdx_0.4.1_linux_arm64.tar.gz)
  * [Windows (x86\_64)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.4.1/kdx_0.4.1_windows_x86_64.zip)

  Available in [kdx-cli v0.4.1](https://github.com/kodexa-ai/kdx-cli-releases/releases/tag/v0.4.1)
</Update>

<Update label="2025-11-27" tags={["CLI"]}>
  ### KDX CLI v0.3.0

  Knowledge Sets & Immutable Resources:

  **New Features:**

  * **Knowledge Set Support**: New `knowledgeset` resource type with full CRUD operations, including example configurations for financial knowledge sets and full support in metadata API and sync operations
  * **Immutable Resource Types**: Added support for immutable resources (`featuretype` and `featureinstance`) that cannot be modified once created, maintaining database integrity. Sync operations automatically skip updates for immutable resources with appropriate warnings

  **Download Links:**

  * [macOS (Intel)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.3.0/kdx_0.3.0_darwin_x86_64.tar.gz)
  * [macOS (Apple Silicon)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.3.0/kdx_0.3.0_darwin_arm64.tar.gz)
  * [Linux (x86\_64)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.3.0/kdx_0.3.0_linux_x86_64.tar.gz)
  * [Linux (ARM64)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.3.0/kdx_0.3.0_linux_arm64.tar.gz)
  * [Windows (x86\_64)](https://github.com/kodexa-ai/kdx-cli-releases/releases/download/v0.3.0/kdx_0.3.0_windows_x86_64.zip)

  Available in [kdx-cli v0.3.0](https://github.com/kodexa-ai/kdx-cli-releases/releases/tag/v0.3.0)
</Update>
