# Migrating a TopLogic application from 8.0.0-alpha12 to 8.0.0-alpha13

Audience: an AI coding agent (or developer) upgrading a TopLogic application project.
Sources: release notes https://dev.top-logic.com/tl/content/releases/TL_8.0.0-alpha13/ and the
TopLogic Trac tickets flagged `RequiresMigration` for milestone `TL_8.0.0-alpha13` (ticket numbers
are given as `#NNNNN`; the per-ticket page is `https://dev.top-logic.com/tl/content/releases/TL_8.0.0-alpha13/NNNNN/`).

## How to use this document

1. Work through the sections in order. Section 1 must be done first (the project does not build
   otherwise); the rest can be done in any order.
2. Every step has a **Detect** line: run the search in the application's source tree. If it finds
   nothing, the step does not apply — skip it. Do not invent changes for steps that do not apply.
3. Many changes are reported as **configuration errors at application start or when a view is
   loaded** (not at compile time). After migrating, start the application, log in, and open every
   view; read the log for `ConfigurationException` / configuration errors.
4. The engine ships developer articles in its jars (`META-INF/tl-docs/**/*.md`). If the `tl-mcp`
   tools are available, read them with `read_doc` (names given below as `doc:<name>`).
5. Ticket descriptions on Trac are partly German; this document translates the instructions.
6. Some steps **delete data** (#30079 comments) or change access rights (#30255). Report them to the
   user before starting the migrated application against a production database.

Search commands below assume the project root as working directory and GNU grep.

---

## 1. Build and platform

### 1.1 Version bump
Set the TopLogic version to `8.0.0-alpha13` (parent `tl-parent-app` / `tl-parent-core` version, or
the `tl.version` property).

### 1.2 Classic UI parts moved into separate modules (#29961) — mandatory for classic applications declaring modules individually
React applications no longer contain modules of the classic UI. Applications using `tl-starter-classic`,
`tl-layout-formeditor` or `tl-chat-classic` need no change; `tl-addons-loginmessages` brings
`tl-addons-loginmessages-core` itself.

A classic application that declares its modules individually and uses none of these adds, depending
on the functions it uses:

| Module to add | Provides |
|---|---|
| `tl-model-search-classic` | model-based search, script console, Ace editor for TL-Script, code and JSON attributes and `@Code` configuration properties. **Without it these attributes are edited without code editor, silently.** |
| `tl-model-wysiwyg` | CKEditor for HTML attributes (before possibly obtained via `tl-starter-react`, `tl-service-openapi-common` or `tl-bpe`) |
| `tl-ai-classic` | classic AI administration (before part of `tl-ai-core`) |
| `tl-monitoring` | classic monitoring UI (before obtained via `tl-layout-view`) |
| `tl-xio-classic` | command `com.top_logic.xio.command.XMLImportCommand` (before part of `tl-xio`); class name and configuration unchanged |

Own code using classes of the classic parts (e.g. `SearchSelectorComponent`, `TLScriptCodeEditorControl`,
`StructuredTextControl`, `StructuredTextConfigService`) needs the respective classic module as
dependency; package and class names are unchanged.

Dropped transitive dependencies: `tl-model-search` no longer brings `tl-reporting-flex` (JFreeChart
reports, classes under `com.top_logic.reporting.flex`) and `tl-layout-codeedit`; `tl-ai-core` no
longer brings `tl-chart-chartjs` (Chart.js diagrams of the classic UI). An application that got these
modules only that way declares them itself or adds `tl-model-search-classic` resp. `tl-ai-classic`.

Applications with classic scripted tests (`*.script.xml`) not generated by the archetype declare the
test jar themselves:
```xml
<dependency>
	<groupId>com.top-logic</groupId>
	<artifactId>tl-layout-scripting-template</artifactId>
	<type>test-jar</type>
	<scope>test</scope>
</dependency>
```

Detect:
```
grep -n -e '<artifactId>tl-' pom.xml */pom.xml 2>/dev/null
grep -rln -e 'SearchSelectorComponent' -e 'TLScriptCodeEditorControl' -e 'StructuredTextControl' \
  -e 'StructuredTextConfigService' -e 'XMLImportCommand' -e 'com.top_logic.reporting.flex' \
  -e 'chartjs' --include=*.java --include=*.xml src
find . -name '*.script.xml' -not -path '*/target/*' | head
```
(Compare the declared modules with the table; a classic application without `tl-starter-classic`
must check every row.)

### 1.3 Classic BPE task processing in `tl-bpe-classic` (#30065)
Classic applications that include `tl-bpe` directly (not via `tl-bpe-modeler`) and use the classic
task processing (layout templates `tl-bpe/startProcess*.template.xml`,
`tl-bpe/advanceProcess*.template.xml`, classes under `com.top_logic.bpe.layout`) or the PDF export add:
```xml
<dependency>
  <groupId>com.top-logic</groupId>
  <artifactId>tl-bpe-classic</artifactId>
</dependency>
```
Applications using `tl-bpe-modeler` or `tl-bpe-app` get `tl-bpe-classic` transitively. A React
application includes `tl-bpe-view` and shows the task page (`bpe/tasks.view.xml`) where it wants it.

Further changes affecting own dependencies and code:
- `tl-bpe` no longer brings `tl-layout-formeditor`. An application using classes or layouts of the form
  editor that got the module only via `tl-bpe` declares `tl-layout-formeditor` itself (or gets it via
  `tl-bpe-classic`).
- `com.top_logic.bpe.modeler.copy.DeepCopy` is now `com.top_logic.bpe.util.DeepCopy` (module `tl-bpe`);
  adapt imports.
- `BPMLUpdateCommand.updateCollaboration(Collaboration, Source, boolean)` and
  `BPMLUpdateCommand.updateCollaborationInTransaction(...)` are removed. Use
  `BPMLDefinitions.updateCollaboration(...)` (with `Source` or `BinaryContent`); the caller opens the
  transaction itself.

Detect: `grep -rn -e '<artifactId>tl-bpe' -e 'startProcess' -e 'advanceProcess' -e 'com.top_logic.bpe.layout' -e 'formeditor' -e 'DeepCopy' -e 'BPMLUpdateCommand' --include=*.xml --include=*.java .`

### 1.4 AI browser tools in the optional module `tl-ai-browser` (#30253)
- An application that uses the browser tools (`web-browser.*`) or cookie pools adds the dependency
  `com.top-logic:tl-ai-browser`. An existing database is taken over on startup without further steps.
- An own agent that browses and names the skill `TL_webAccess` additionally names the skill `TL_webBrowser`.
- The classes moved to the package `com.top_logic.tl.ai.browser` (`BrowserToolsService`, `BrowserTool`, …)
  and `com.top_logic.tl.ai.browser.cookiepool` (`CookiePoolService`); configuration and code
  referencing them by class name must be adapted, e.g.
  `<config service-class="com.top_logic.tl.ai.browser.BrowserToolsService">`.
- An application without `tl-ai-browser` whose database already contains cookie pools keeps the table
  `CookiePool` unused; `doc:ai/browser` describes the migration that removes it.
- An exclusion of `driver-bundle` from `tl-ai-core` (workaround) can be removed.

Detect: `grep -rn -e 'web-browser' -e 'TL_webAccess' -e 'BrowserToolsService' -e 'BrowserTool' -e 'CookiePool' -e 'driver-bundle' .`

---

## 2. Server configuration and database

### 2.1 Scheduler: one active/inactive switch per task (#30057) — configuration
- The task configuration property `blocked-by-default` is removed. Replace `blocked-by-default="true"`
  with `enabled="false"`.
- The task configuration property `blocking-allowed` is removed without replacement; every task can be
  activated and deactivated by an administrator.
- Blocking states stored in the database are taken over automatically (blocked = inactive, unblocked = active).

Java API and classic layouts: see 5.6.

Detect: `grep -rn -e 'blocked-by-default' -e 'blocking-allowed' .`

### 2.2 External login: `user-mapping` / `user-name-extractor` only in element form (#30172)
The configuration properties `user-name-extractor` and `user-mapping` (pac4j clients in
`Pac4jConfigFactory`, `J2eeRemoteUserAuthenticationServlet`) no longer accept a bare class name as
attribute value. Replace
```xml
<keycloak name="..." user-mapping="com.example.MyMapping">
```
by
```xml
<keycloak name="...">
  <user-mapping class="com.example.MyMapping"/>
</keycloak>
```
The element form `<user-mapping class="..." .../>` with further attributes stays unchanged. Java code
reading `getUserMapping()` / `getUserNameExtractor()` from the client configuration obtains the
instances from `Pac4jConfigFactory.getUserMapping(String)` / `getUserNameExtractor(String)` instead.

Detect: `grep -rn -e 'user-mapping="' -e 'user-name-extractor="' -e 'getUserMapping()' -e 'getUserNameExtractor()' .`

### 2.3 Contact configuration (#30079)
The file `WEB-INF/conf/contactConf.xml` is gone; `metaConf.txt` of `tl-contact` includes
`contactConf.config.xml` directly. An application that overrides or references `contactConf.xml` in
its own `metaConf.txt` must adapt this.

Detect: `grep -rn 'contactConf.xml' .`

### 2.4 AI model catalog: Claude 5.5 (#30251)
- An application relying on the shipped model lists (no `tl_anthropic_models` / `tl_xai_models`) now
  runs its agents on `claude-sonnet-5-5` (default) resp. offers `grok-4.7,grok-4.3`. To keep the
  previous models, set the variables (`tl_anthropic_models=claude-sonnet-4-6,claude-opus-4-7,claude-haiku-4-5`,
  `tl_xai_models=grok-4.3,grok-4-1-fast`). Note: `grok-4-1-fast` is no longer in the bundled catalog
  (next bullet) — declare it in `<model-overrides>` if you keep it.
- An agent tool access policy (`AiToolAccessService`) may grant only models a configured provider
  offers; otherwise the policy is refused and the service does not start. A policy granting a model
  no longer in the shipped lists (e.g. `<grant models="claude-haiku-4-5"/>`) changes the grant to a
  current model (`claude-haiku-5-5`) or sets `tl_anthropic_models` / `tl_xai_models` to include it.
- A model the bundled catalog no longer knows (retired models such as `claude-sonnet-4-20250514`,
  `claude-opus-4-1`, `grok-4-1-fast`) gets no context window, output limit and prices. Switch to a
  current model or declare it in the `<model-overrides>` of `LLMProviderService`.
- Java code constructing `ModelInfo.ModelCost` / `ModelInfo.TieredCost` passes a list of tiers resp.
  the tier's threshold; `CostCalculator.TIER_THRESHOLD` is gone (`ModelCost.tierFor(promptTokens)`
  answers the applicable tier).

Detect:
```
grep -rn -e 'tl_anthropic_models' -e 'tl_xai_models' -e 'models=' -e 'model-overrides' -e 'claude-' -e 'grok-' \
  -e 'ModelCost' -e 'TieredCost' -e 'TIER_THRESHOLD' .
```

### 2.5 Configuration variables are overridden from the environment (#30268)
A configuration variable that the application overrides with a fixed value without `${env:…}`
expression (e.g. `<alias name="%STORAGE_PATH%" value="./data"/>`) is now still replaced by `tl_<name>`
from JNDI, system property or environment (here `tl_storage_path`). If the fixed value must win even
when the environment sets `tl_<name>` (e.g. a Docker image setting `tl_storage_path`), remove the
variable from the installation's environment or set the wanted value there.

Detect: `grep -rn '<alias name="%' --include=*.xml .` (then check the deployment environment for `tl_<name>`)

### 2.6 Self-service (#30041, #30183, #30223) — configuration and texts
- The login hook class `com.top_logic.security.selfservice.invitation.CreateLoginHoook` is renamed to
  `CreateLoginHook`. An application referencing it (e.g. in `<login-hooks>` of `MainLayout$GlobalConfig`)
  or in code must use the new name.
- An application that overrides the keys `tl.security.selfservice.mail.*` in its own bundle moves the
  texts to the options `invitation-text`, `verification-text`, `reset-password-text`, `reset-mfa-text`
  of `InvitationModule`. The bundle `tl-security-selfservice-core.app-messages` no longer exists; a
  configuration referencing it drops the reference.
- An application that redefines a mail script may use the configured texts, passed as fourth and
  fifth argument (subject, body), unfilled.
- An application with `tl-security-selfservice-core` starts the `CaptchaService`. If a captcha is used
  in a cluster, configure a shared key (`<hmac-key name="tl_captcha_hmac_key"/>`) and serve the
  application via HTTPS.
- Resource keys removed from `com.top_logic.security.selfservice.invitation.I18NConstants`:
  `ERROR_NO_INVITATION__ID`, `UPDATED_INVITATION_CODE`, `DELETED_INVITATION`, `ERROR_CODE_MISMATCH`,
  `ERROR_CODE_MISMATCH_TOO_MANY_TIMES`, `REQUEST_CODE_NOT_ALLOWED__TIMEOUT`. An application overriding
  them drops them.
- Classic layouts changed in the layout editor: an application that stored its own copy of
  `admin/security/accounts/existingInvitations/*` or `admin/security/accounts/inviteUsers/dialog.layout.xml`
  re-applies the new columns and commands (incl. the field / column `language`) to it.
- Automatic: existing invitations get an expiry date (update time plus `invitation-validity`) and no
  language (sent in the application's default language); schema (attribute `locked`, type
  `Registration`) and the form definition of the user administration are migrated at startup.

Java API: see 5.12.

Detect:
```
grep -rn -e 'CreateLoginHoook' -e 'tl.security.selfservice.mail' -e 'selfservice-core.app-messages' \
  -e 'ERROR_NO_INVITATION' -e 'UPDATED_INVITATION_CODE' -e 'DELETED_INVITATION' -e 'ERROR_CODE_MISMATCH' \
  -e 'REQUEST_CODE_NOT_ALLOWED' -e 'CaptchaService' -e 'tl-security-selfservice' .
find . -path '*layouts/admin/security/accounts*' -not -path '*/target/*'
```

### 2.7 Unique link tables and bag tables (#30031) — check own link tables
The migration `Ticket_30031_unique_link_tables` (`tl-element`) runs automatically: it creates the bag
tables, moves the links of all bag references there, removes duplicate links (same `source`,
`metaAttribute`, `dest` in overlapping revisions) over the whole history from the standard tables
(incl. application tables derived from them, each cleaned link is logged) and replaces the `source`
index by a unique index.

Applications with own link tables derived from `hasWrapperAttValueBaseAssociation` inherit the unique
index. If they store bag references (explicitly via `<storage table="..."/>`), those links are not
cleaned; the migration reports them as errors and creating the index fails on duplicates. Such
references must be reconfigured to a bag table (derived from `hasWrapperAttValueBagBaseAssociation`)
and their links moved with `<move-links>` in an own migration.

Java API: `ListStorage.listConfig(...)`, `SetStorage.setConfig(...)` and
`ImageGalleryStorage.imageGalleryConfig(...)` have an additional parameter `boolean bag` (previous
behaviour: `false`; `true` for a bag reference).

Detect:
```
grep -rn -e 'hasWrapperAttValueBaseAssociation' -e '<storage table=' --include=*.xml .
grep -rn -e 'listConfig(' -e 'setConfig(' -e 'imageGalleryConfig(' --include=*.java .
```

### 2.8 Own migration processors: `SQLFactory` and `DBHelper` (#30163, #30167)
- `SQLFactory.modifyColumnName(SQLTable, String, DBType, String)`, `modifyColumnType(SQLTable, String, DBType)`
  and `modifyColumnMandatory(SQLTable, String, DBType, boolean)` are removed. Callers pass the full
  definition of the column after the change:
  ```java
  modifyColumnName(table, columnName, newName, type, mandatory, binary, size, precision)
  modifyColumnType(table, columnName, type, mandatory, binary, size, precision)
  modifyColumnMandatory(table, columnName, type, mandatory, binary, size, precision)
  ```
  or the variants with a `DBColumn` (`modifyColumnName(table, column, newName)`,
  `modifyColumnType(table, column)`, `modifyColumnMandatory(table, column)`). Values formerly set via
  setters on `SQLModifyColumn` (`setSize(..)`, `setBinary(..)`, `setPrecision(..)`, `setMandatory(..)`)
  go into these parameters:
  ```java
  // before
  modifyColumnType(table("T"), "C", DBType.STRING).setSize(150).setBinary(true)
  // after
  modifyColumnType(table("T"), "C", DBType.STRING, mandatory, true, 150, 0)
  ```
  `mandatory` is the mandatory flag the column shall have after the change (for a pure type change
  its current one).
- `SQLFactory.addColumn(..)` has no parameter `defaultValue` any more; `SQLAddColumn.getDefaultValue()`
  / `setDefaultValue(..)` are gone. A processor adding a mandatory column with default creates the
  column non-mandatory, fills it with `UPDATE`, then sets it mandatory with `SQLFactory.modifyColumnMandatory(..)`.
- Own SQL dialects (subclasses of `DBHelper`): the last parameter `Object defaultValue` of
  `appendChangeColumnName(..)`, `appendChangeColumnType(..)` and `appendChangeMandatory(..)` is gone;
  `appendDefaultValue(..)` and `appendDBType(Appendable, DBType, String, long, int, boolean, boolean, Object)`
  no longer exist (use the variant without default value).
- `AddDatabaseColumnProcessor`: `default-value` no longer creates a database default, it only fills
  existing rows. A permanent database default needs an own `sql` step.

Detect:
```
grep -rn -e 'modifyColumnName' -e 'modifyColumnType' -e 'modifyColumnMandatory' -e 'addColumn(' \
  -e 'DefaultValue' -e 'extends .*DBHelper' -e 'appendChangeColumn' -e 'appendChangeMandatory' -e 'appendDBType' \
  --include=*.java .
grep -rn -e 'default-value' --include=*.xml src/main/webapp/WEB-INF/conf 2>/dev/null
```

---

## 3. Security / access checks

### 3.1 Group mapper of the `ElementAccessManager` removed (#30096) — mandatory if configured
- Remove the entry `<group-mapper .../>` from the configuration of the `AccessManager`.
- Every role rule of type `reference` whose path ends at a `Contacts:PersonContact` gets an additional
  step to the person:
  ```xml
  <path>
  	<step attribute="..."/>
  	<step attribute="tl.accounts:Person#contact" inverse="true"/>
  </path>
  ```
- Own `Mapping` implementations with more logic than "group or person to group" must be expressed as
  navigation steps in the affected role rules. If the mapping is not yet a reference in the model,
  model it there first.
- The classes `com.top_logic.element.boundsec.manager.rule.GroupMapper`,
  `com.top_logic.tool.boundsec.manager.SimpleGroupMapper` and
  `com.top_logic.contact.boundsec.manager.rule.ContactGroupMapper` are removed; remove own code using
  or extending them.

Detect: `grep -rn -e 'group-mapper' -e 'PersonContact' -e 'Mapping' -e 'GroupMapper' --include=*.xml --include=*.java .`

### 3.2 Comments module and mandator-aware contacts removed (#30079) — deletes data
**Comments:** the migration `tl-element:Ticket_30079_Remove_comments` deletes the module `tl.comments`
and the table `Comment`. **All existing comments are deleted.** An application that declares
attributes of type `tl.comments:Comment` in its model must
1. remove these attributes from its model (`*.model.xml`) and from layouts, forms and the security
   configuration (grants on the module `tl.comments`),
2. add an own migration that depends on `tl-element:Ticket_30079_Remove_comments` and deletes the
   attributes in its `pre-processors`, e.g.:
   ```xml
   <dependencies>
   	<dependency name="Ticket_30079_Remove_comments" module="tl-element"/>
   	...
   </dependencies>
   <pre-processors>
   	<!-- Reference: also deletes the stored links. -->
   	<delete-reference name="my.module:MyType#comments"/>
   	<!-- Derived property. -->
   	<delete-property name="my.module:OtherType#comments"/>
   </pre-processors>
   ```
3. remove usages of the template `commons/comments/comments.xml` and of classes from
   `com.top_logic.element.comment`.

**Mandator-aware contacts:** an application using classes from `com.top_logic.contact.mandatoraware`
or `com.top_logic.contact.search` (e.g. `COSPersonContact` / `COSCompanyContact` as implementation of
contact types, the SAP/Excel importers or `ContactSearchComponent`) must remove them or replace them by
own implementations. Usages of the filter `SupportsContacts` or the generator `CompanyContact` of
`FilterFactory` / `GeneratorFactory` must be removed. Likewise
`com.top_logic.contact.filter.ContactListModelBuilder` and
`com.top_logic.contact.external.importer.ExcelExternalContactImporter` are removed; remove their usages
in layouts, configuration and code.

Detect:
```
grep -rn -e 'tl.comments' -e 'commons/comments' -e 'com.top_logic.element.comment' \
  -e 'contact.mandatoraware' -e 'contact.search' -e 'COSPersonContact' -e 'COSCompanyContact' \
  -e 'ContactSearchComponent' -e 'SupportsContacts' -e 'CompanyContact' \
  -e 'ContactListModelBuilder' -e 'ExcelExternalContactImporter' .
```

### 3.3 Attribute WRITE grants checked on objects created in the same transaction (#30255)
An application script that runs with security (the default) and sets an attribute with an
attribute-level WRITE grant on an object created in the same transaction — e.g. a "created by" field
the user may not edit — is now refused for a user without that grant. Either grant the role the right
on the attribute or run that script without security.

Workarounds for the former wrong refusal of the pre-commit guard (e.g. a `SecurityContextFactory`
answering `canCreate` with yes for a container created in the same turn) can be removed.

The classes `com.top_logic.tl.ai.core.security.PreCommitGuard` and `ModelSecurityPreCommitGuard` are
removed. Remove own code using or implementing them; the tools check the rights of a write themselves
(Java tools via `ModelSecurity`, TL-Script via the engine).

Detect: `grep -rn -e '<grant' --include=*.xml . | grep -i attribute` and check scripts that create
objects and set such attributes (creation commands, `<execute-script>`, AI tools);
`grep -rn 'PreCommitGuard' --include=*.java --include=*.xml .`

---

## 4. React views (`*.view.xml`)

### 4.1 Sidebar of the shell in sections (#30044) — mandatory for shell overlays
The shell structures its sidebar into `<section id="application"/>` (empty, filled by the application)
and `<section id="system">` with the administration. An overlay of the shell
(`WEB-INF/views/app.view.xml` of an application) that still names `config:reference="system-separator"`
or uses `<separator>` or `<header-item>` can no longer be loaded (the application answers `/view/`
with HTTP 500). Adapt:
- Entries placed before the system section with `config:position="before" config:reference="system-separator"`
  are written into the application section without these two attributes:
  ```xml
  <sidebar config:ref="sidebar">
    <items>
      <section id="application">
        <nav-item id="home">...</nav-item>
      </section>
    </items>
  </sidebar>
  ```
- A `<separator/>` between own entries: put the entries before and after it into one
  `<section id="…">` each.
- A `<header-item id="x">` with label: replace it by `<section id="x">` with the same label (optional
  `label`, `icon`) and move the entries it covered into the section. Entry ids stay unchanged.

Detect: `grep -rn -e 'system-separator' -e '<separator' -e '<header-item' --include=*.view.xml .`

### 4.2 Colour role of rows, cells and displays (#30256)
A script in `row-class`, `cell-class` or `css-class` (on `ResourceDisplayByExpression`) that yields one
of the classes `tl-status-error`, `tl-status-warning`, `tl-status-success` or `tl-status-info` moves to
the corresponding role property and yields the role name without prefix:
```xml
<table row-class="$row.get(`my:Part#amount`) > 100 ? 'tl-status-warning' : null"/>
```
becomes
```xml
<table row-role="$row.get(`my:Part#amount`) > 100 ? 'warning' : null"/>
```
Likewise `cell-class` → `cell-role` on the column and `css-class` → `role` on
`ResourceDisplayByExpression`. A script that yields further own classes besides the status class keeps
those in the class property. A display with a role appears as a pill, not as coloured text.

The TL-Script functions `securityGrantsRowClass` and `securityGrantsCellClass` are renamed to
`securityGrantsRowRole` and `securityGrantsCellRole`, yield a role name and belong into `row-role`
resp. `cell-role`.

Java: `RowClassFunction` is renamed to `RowFunction` (`RowFunction.rowClass(...)`, `RowFunction.rowRole(...)`);
the record `ColumnSetup` has the additional component `cellRole` after `cellClass`.

Detect: `grep -rn -e 'tl-status-' -e 'securityGrantsRowClass' -e 'securityGrantsCellClass' -e 'RowClassFunction' -e 'ColumnSetup(' .`

### 4.3 Showing a unit only for one type: `<visibility>` instead of `<access-control type>` (#30258)
An `<access-control>` whose `type` only serves to hide a unit for objects of other types is replaced by
```xml
<visibility inputs="..." expr="$x.instanceOf(`<Type>`)"/>
```
(or complemented by it, if an access check is wanted too; its `type` is then a type all checked
objects have).

Detect: `grep -rn '<access-control' --include=*.view.xml . | grep 'type='`

### 4.4 `<job-status>` closable by default (#30250)
`<job-status>` offers a close button for an ended job by default, which clears the input channel.
Where the display of an ended job must stay, set `closable="false"`.

A cancelled job whose body returns a value regularly after the cancellation delivers this value as
`jobResult(...)`.

Detect: `grep -rn -e '<job-status' -e 'jobResult' --include=*.view.xml .`

### 4.5 Self-service views renamed (#30183)
React (`tl-layout-view-selfservice`, affects preview versions with #30041): the view
`invitation-code.view.xml` is now `verification-code.view.xml`, the action `<check-invitation-code/>`
is `<check-verification-code/>`, `<accept-invitation/>` is `<create-own-account/>`. An application that
overrides these views or uses the actions in own views renames them.

Detect: `grep -rn -e 'invitation-code' -e 'check-invitation-code' -e 'accept-invitation' .`

### 4.6 Agents and tools at `/agent-api` (#30200)
The key commands are gone. Objects are addressed with the IDs from the observation
(`key` / `nodeId` / `rowId` / `value`, see the respective argument schema); the table takes `rowId`
instead of `rowIndex`, `drop` expects `keys` as JSON array. Argument schemas are under `schemas` and
referenced via `$ref`; there is no `params` field any more. The compact view is called `mode=actions`,
`pretty=true` gives indented output. Adapt own clients, agent prompts and recorded scripts.

Detect: `grep -rn -e 'agent-api' -e 'rowIndex' -e 'ByKey' -e '"params"' .`

### 4.7 Log view of the administration moved (#30059)
The view `admin/system/logs.view.xml` (module `tl-layout-view`) is now `admin/monitoring/logs.view.xml`.
A same-path overlay of the application (`WEB-INF/views/admin/system/logs.view.xml`) or a
`<view-ref view="admin/system/logs.view.xml"/>` must use the new path; an overlay of the old path has no
base any more.

Detect: `grep -rn 'admin/system/logs' . ; find . -path '*WEB-INF/views/admin/system/logs.view.xml' -not -path '*/target/*'`

---

## 5. Java APIs and TL-Script

### 5.1 View commands without `HandlerResult` (#30151)
Own `ViewCommand` implementations, `CommandModel`s, `ButtonAction`s or `@ReactCommandHandler` methods:
- return type `void` instead of `HandlerResult`; `return HandlerResult.DEFAULT_RESULT;` is dropped;
- `return HandlerResult.error(key)` becomes `throw new TopLogicException(key)`;
  `return HandlerResult.notExecutable(...)` resp. checking an `ExecutableState` becomes
  `throw CommandErrors.notExecutable(state)`;
- a caller evaluating `command.execute(...)` catches `TopLogicException` instead of checking `isSuccess()`;
- a handler answering its caller declares the answer as msgbuf message and returns it instead of
  `ReactCommandReply.create().put(...).toResult()`. Client code reads the answer fields as before; a
  failed command now answers with `success: false` and `error`.

Detect: `grep -rn -e 'ViewCommand' -e 'CommandModel' -e 'ButtonAction' -e 'ReactCommandHandler' -e 'ReactCommandReply' --include=*.java . | grep -v '^.*import'`

### 5.2 Command lists and `ViewCommandModel` (#30063, #30207)
- Code reading or setting the command list of a configuration of `CommandCarrierElement` or
  `FormElement` (`getCommands()`) gets `List<ViewCommand.Config>` instead of
  `List<PolymorphicConfiguration<? extends ViewCommand>>`. `.view.xml` files are unchanged.
- `ViewCommandModel.execute(ReactContext, Object)` is now `execute(Object)`; the command runs in the
  context the model was created with.
- The public constructors of `ViewCommandModel` and `ViewUploadCommandModel` expect the `ViewContext`
  as first argument. `ViewCommandModel.create(...)` / `forCommand(...)` no longer accept a `null` context.
- `RowCommandColumn.RowCommand.of(...)` expects no context any more.
- An own wrapper around a `ViewCommandModel` that only sets another execution context is superfluous.

Detect: `grep -rn -e 'CommandCarrierElement' -e 'FormElement' -e 'ViewCommandModel' -e 'ViewUploadCommandModel' -e 'RowCommand.of' --include=*.java .`

### 5.3 Own React controls: stable object IDs for scripting (#30200)
- Subclasses of `ReactControl` overriding `scriptingScalarState()` override
  `scriptingScalarState(ProjectionMode mode)` instead (the variant without parameter is `final`).
  Implementations of `ScriptingNode.scriptingState()` become `scriptingState(ProjectionMode mode)`. An
  object is entered into the projection with `ScriptingModelKey.putIdentity(projection, mode, objectIdentities(), ...)`
  instead of by its key.
- A control whose commands address objects holds their IDs in an `ObjectIdIndex`, returns it from
  `objectIdentities()` and marks the ID arguments of its typed argument interfaces with
  `@DisplayedObjectId` (lists additionally with `@JsonBinding(SimpleJsonListBinding.class)`). A
  `recordCommand` override that only translates IDs into keys is dropped.
- Removed types: `ModelKeyArguments` (constant `KEY` is now in `ScriptingModelKey`),
  `SelectByKeysArguments`, `SelectByNameArguments`, `DropObjectsArguments` and the `*ByKeyArguments`
  of table and tree.

Detect: `grep -rn -e 'scriptingScalarState' -e 'scriptingState(' -e 'ModelKeyArguments' -e 'SelectByKeysArguments' -e 'SelectByNameArguments' -e 'DropObjectsArguments' -e 'ByKeyArguments' -e 'recordCommand' --include=*.java .`

### 5.4 Model value listeners (#30132)
An own class implementing `com.top_logic.layout.form.ValueListener` that is configured in a model
annotation `<value-listeners>` must implement `com.top_logic.model.annotate.AttributeValueListener`
instead: `valueChanged(TLFormObjectBase object, TLStructuredTypePart attribute, Object oldValue, Object newValue)`
receives the edited object (form overlay) and the attribute instead of the `FormField`. Model files and
`<script>` listeners are unchanged.

Detect: `grep -rn -e 'implements .*ValueListener' -e 'value-listeners' .`

### 5.5 `DynamicVisibility` moved (#30205)
`DynamicVisibility` is now in package `com.top_logic.model.annotate` (module `tl-core`) instead of
`com.top_logic.layout.view.form`. Adapt imports:
```java
import com.top_logic.model.annotate.DynamicVisibility;
```
Detect: `grep -rn 'layout.view.form.DynamicVisibility' --include=*.java .`

### 5.6 Scheduler API and classic scheduler layouts (#30057)
- `Scheduler.enable(Task)` / `disable(Task)` / `isTaskEnabled(Task)` / `isEnabled(Task)` and
  `blockTask(Task)` / `unblockTask(Task)` / `isTaskBlocked(Task)` are replaced by `activate(Task)` /
  `deactivate(Task)` / `isActive(Task)` / `resetActive(Task)`. `Scheduler.isBlockingAllowed(Task)` and
  `Task.isBlockingAllowed()` are removed.
- Classic scheduler view: the command handlers `blockTask` / `unblockTask` are replaced by
  `deactivateTask` / `activateTask` (plus `resetTaskActive`); the classes moved from
  `com.top_logic.util.sched.layout.block` to `com.top_logic.util.sched.layout.active`. The table
  columns `isBlocked`, `isEnabled` and `isBlockingAllowed` are replaced by `active`. Application layouts
  referencing them must be adapted.
- A customer component library replacing the React part `FieldValue` receives the optional props
  `preformatted` and `action`; the part list contains the part `CopyButton`.
- Further removed API: the filters `com.top_logic.util.sched.entry.filter.EnabledSchedulerEntryFilter`
  and `ManuallyStartedSchedulerEntryFilter`, the classes `BlockState` and `BlockStateRenderer`
  (`com.top_logic.util.sched.layout.block`), `TaskImpl.isBlockedByDefault()`, and on `SchedulerEntry`
  the constructor `SchedulerEntry(Task, boolean)` and the methods `setEnabled` / `isEnabled` /
  `setAttached` / `isAttached` / `createThread`. Own layouts and code using them ask
  `Scheduler.isActive(Task)` instead or drop the usage.

Detect: `grep -rn -e 'blockTask' -e 'unblockTask' -e 'isTaskBlocked' -e 'isTaskEnabled' -e 'isBlockingAllowed' -e 'sched.layout.block' -e 'isBlocked' -e 'FieldValue' -e 'SchedulerEntryFilter' -e 'BlockState' -e 'SchedulerEntry' .`

### 5.7 `ModificationListener` and `Committable` (#30218, #30225)
- `ModificationListener.createModification(KnowledgeBase, CommitActions, Map created, Map updated, Map removed)`
  becomes `createModification(KnowledgeBase, CommitActions, PendingChanges changes)`; read the maps via
  `changes.getCreated()`, `changes.getUpdated()`, `changes.getRemoved()`.
  `ModificationListener.notifyUpcomingDeletion(KnowledgeBase, CommitActions, KnowledgeItem)` gets the
  `CommitActions` handle as additional second parameter (as `createModification` already does).
- `Committable.complete(CommitContext)` is removed. Move such code to an `AfterCommitAction`
  (registered on the `Transaction`, or in a `ModificationListener` on the `CommitActions` handle; the
  action receives the `UpdateEvent` of the commit) and drop the `@Override` method.
- An implementation of `StorageImplementation` that implements both `ColumnStorage` and
  `SeparateTableStorage` must override `addChangeLocators(...)` and `canDetectValueChanges()` to combine
  both behaviours (the compiler reports the conflicting default methods).

Detect: `grep -rn -e 'ModificationListener' -e 'createModification' -e 'notifyUpcomingDeletion' -e 'complete(CommitContext' -e 'implements .*ColumnStorage' -e 'SeparateTableStorage' --include=*.java .`

### 5.8 Background jobs and thread bindings (#30196, #30250)
- `ThreadLocalBinding`, `BindingRegistry` and `BindingSnapshot` moved from `com.top_logic.tl.ai.core.binding`
  to `com.top_logic.basic.thread`. Adapt imports.
- Cancelling a `<start-job>` job or stopping a scheduler task no longer interrupts the thread. Code
  relying on being woken up (own `Thread.sleep`, `Object.wait`, blocking I/O, `Future.get`) runs such
  waits with `CancelScope.sleep(...)` resp. `CancelScope.runInterruptibly(...)` — never with database
  accesses inside.
- Own implementations of `JobMonitor` implement `uncancelable()` (a delegating implementation passes
  it to the wrapped monitor).
- The record `JobState` has the additional component `cancelable` (before `control`); code constructing
  `JobState` (e.g. tests) passes it.
- `ViewActionChain.run(..., Consumer onComplete, Runnable onSettled)` is gone; instead of the
  `onSettled` callback `ViewActionChain.run(context, actions, arguments, after, onComplete, holds)` takes
  a `ViewActionChain.HoldObserver`.

Detect: `grep -rn -e 'tl.ai.core.binding' -e 'Thread.sleep' -e '\.wait(' -e 'JobMonitor' -e 'JobState' -e 'ViewActionChain.run' --include=*.java .`

### 5.9 Login methods (#30067)
An own implementation of `com.top_logic.base.accesscontrol.loginmethod.LoginMethod` replaces
`getInitiationUrl(String returnToUrl)` by `getInitiationUrl(String returnToUrl, String windowName)` and
adds the window name (if not `null`) as parameter `ExternalAuthenticationServlet.WINDOW_PARAM` to the
initiation URL, see `Pac4jLoginMethod`.

Detect: `grep -rn -e 'LoginMethod' -e 'getInitiationUrl' --include=*.java .`

### 5.10 Chat API (#30126)
Only for applications that address the chat API themselves or use `tl-chat` classes:
- The JSON API `/chat/*` (`ChatServlet`) is gone; an own client calls the commands of the chat controls
  (name constants in `ChatControl`), e.g. via the headless access `/agent-api`.
- `ChatSessionRegistry`: `windowId(session, subSession)`, `resolveSubSession(session, windowId)` and
  `resolveTracker(session, windowId, conversation)` are gone. The tracker of a window is
  `tracker(subSession, conversation, account)`; the window name is on the sub-session
  (`subSession.getLayoutContext().getWindowId().getWindowName()`).
- `TaskProgressTracker`: `get/setUserName`, `get/setWelcomeName` and `get/setWindowId` are gone;
  `getAccount()` returns the tracker's account.
- `ChatViewBinding`: `writeSharedState`, `subjectOf`, `windowId`, `contextPath`, `userName`,
  `welcomeName`, `language` and `authenticated` are gone (the state is written on every render by
  `writeRenderState`). `presentable` and `presentableNames` additionally expect the account of the run.
- `useTLCommand()` and `useTLUpload()` (TypeScript) now return the command's answer
  (`Promise<CommandResponse | undefined>` instead of `Promise<void>`); callers ignoring the result are unchanged.

Detect: `grep -rn -e '/chat/' -e 'ChatServlet' -e 'ChatSessionRegistry' -e 'TaskProgressTracker' -e 'ChatViewBinding' -e 'useTLCommand' -e 'useTLUpload' .`

### 5.11 AI tools: commit messages (#30276)
- Own `Tool` implementations calling `TransactionScope.begin(ResKey)`: the method is gone; set the
  message on commit with `TransactionScope.commit(ResKey)`.
- An own writing tool may override `commitMessage(arguments, context)` (or set
  `@ToolMethod(commitMessage = ...)`) to describe its changes in the history; otherwise "Werkzeugaufruf <Name>"
  (tool call) appears.
- Callers of `ScriptToolStore.create` / `update` additionally pass the commit message expression
  (`null` for none resp. unchanged).

Detect: `grep -rn -e 'TransactionScope.begin' -e 'ScriptToolStore' -e '@ToolMethod' --include=*.java .`

### 5.12 Self-service Java API (#30041, #30183, #30223)
- Code constructing `CreateAccountDialog` passes no continuation command and passes an
  `AccountRequest` (e.g. `new InvitationRequest(invitation)`) instead of an `Invitation`.
- `CheckInvitationCode` is renamed to `CheckAccountCode`.
- `LoginViewDialog.loginUserAndReload(...)` now returns a `HandlerResult`: a caller passes an
  unsuccessful result (lock, maintenance mode) on to its caller instead of ignoring it.
- Code calling `sendInvitation` for an invitation that is not open gets an error; an expired invitation
  is sent again with `invitationResend`.
- Code calling `InvitationModule.getInvitationMail()`, `getVerificationMail()`, `getResetPasswordMail()`
  or `getResetMFAMail()` uses the operations `sendInvitation`, `sendVerificationMail`,
  `sendResetPasswordMail` or `sendResetMFAMail` instead.

Detect: `grep -rn -e 'CreateAccountDialog' -e 'CheckInvitationCode' -e 'loginUserAndReload' -e 'sendInvitation' -e 'InvitationModule' --include=*.java .`

### 5.13 React configuration editor: `ConfigAnnotationControlProvider` (#30266)
Only for applications with an own `ConfigAnnotationControlProvider`: a provider no longer refuses a
property by returning `null` from `createControl(...)` but via `accepts(ConfigFieldModel model, A annotation)`.
`createControl` is only called for accepted properties and must not return `null`. Move the former
`null` condition into an override of `accepts` (e.g. `return ConfigControlService.holdsText(model);`).
If the field should take the whole line (multi-line content), also override
`rendersWholeLine(ConfigFieldModel, A)` with `true`.

Detect: `grep -rn 'ConfigAnnotationControlProvider' --include=*.java .`

### 5.14 TL-Script: `try()` passes a `ResKey` to `catch` (#30265)
The catch function of `try()` always receives the error message as `ResKey`, no longer as string. No
change is needed for display and `log()`. A script that processes the message as string (length,
regular expression, comparison with a string, concatenation) converts it with `localize()` first:
```
try(..., catch: msg -> $msg.localize().contains("..."))
```
Detect: `grep -rn 'catch:' --include=*.xml --include=*.script --include=*.tls .` (and scripts stored in the database, e.g. via the model editor)

### 5.15 Scripted tests: `LastRevisionNamingScheme` (#29847)
A script that assigns `LastRevisionNamingScheme` after a commit of another user than the one executing
the assignment must execute the assignment as the committing user (`user-id` of the `SetGlobalVariableOp`).

Detect: `grep -rln 'LastRevisionNamingScheme' --include=*.script.xml .`

### 5.16 Sorting helpers of React table columns (#30204)
`ColumnProviderService.labelOrder()`, `ColumnProviderService.unreadableLast(Comparator)` and
`ScriptedFilter.order(Comparator)` are removed. Own Java code building the sort of a React table column
with them uses `ColumnProviderService.cellSort(...)` instead.

Detect: `grep -rn -e 'labelOrder' -e 'unreadableLast' -e 'ScriptedFilter' --include=*.java .`

### 5.17 `OpenApiServlet` has a new super class (#30177)
`OpenApiServlet` now extends `NoContextServlet` instead of `AbstractTopLogicServlet`; the method
`serviceWithLogMark(HttpServletRequest, HttpServletResponse)` is removed. An own subclass of
`OpenApiServlet` overrides `doService(HttpServletRequest, HttpServletResponse)` instead.

Detect: `grep -rn -e 'extends OpenApiServlet' -e 'serviceWithLogMark' --include=*.java .`

---

## 6. Stylesheets and themes

### 6.1 Theme role `surface-interactive-selected` renamed (#30000)
`--tl-surface-interactive-selected` → `--tl-surface-interactive-strong` (neutral grey, no longer a
selection). There is no alias. Stylesheets and MUI themes that drew a selection with the old name read
the new selection roles instead:

| Old | New |
|---|---|
| `--tl-surface-interactive-selected` (neutral strong fill: track, skeleton, pill, avatar, drop target, field button hover) | `--tl-surface-interactive-strong` |
| `--tl-surface-interactive-selected` (selected row, node, item) | `--tl-surface-selected`, hover `--tl-surface-selected-hover`, edge `--tl-border-selected` |

New roles: `surface-selected`, `surface-selected-hover`, `border-selected`. Customer themes get the
selection roles computed by `derive`; MUI themes feed MUI's selected state into `surface-selected`.
The complete table is in the design system repository, `docs/migration/altlast.md`.

Detect: `grep -rn 'surface-interactive-selected' --include=*.css --include=*.ts --include=*.tsx --include=*.js --include=*.json --include=*.xml .`

---

## 7. Further notes (no action required)

- **Duplicate links (#30031):** the automatic migration logs every link it removed or shortened; read
  the migration log after the first start.
- **Agent screenshots (#30054):** screenshots from before this release belong to no chat; the data
  migration deletes them.
- **Mandatory columns in migrations (#30167):** adding a mandatory column to a large table now takes
  longer (measured on PostgreSQL 15: about 12 s per million rows) and needs about twice the table's
  space until the next `VACUUM`. Database defaults already present stay untouched. In the UI,
  `default-value` of `AddDatabaseColumnProcessor` is labelled "Fill value"; the configuration name is unchanged.
- **Dynamic visibility (#30205):** an attribute whose dynamic visibility yields `mandatory` now prevents
  saving a React form or row-edit table with an empty value; with `read-only`, `disabled`, `hidden` or
  `editable` an attribute declared mandatory in the model is no longer checked as mandatory.
- **Security parents of structured elements (#30079):** `StructuredElementWrapper` no longer overrides
  `getSecurityParents()`, so configured security parents now also apply to structured elements;
  `Document` returns its containing `WebFolder` in addition to the configured security parents; a
  deleted object has no security parents. Users may inherit roles they did not have before — check the
  access rights of structured types with a configured security parent.
- **Claude 5.5 workarounds (#30251):** workarounds in `<model-overrides>` (e.g. `reasoning="false"` for
  `claude-haiku-5-5`) can be removed.
- **Self-service mails (#30223):** a self-service mail that cannot be sent is reported as warning in the
  application log; the default scripts no longer show the English failure text to the user.
- **Tables (#30227):** new theme role `surface-layer-muted` and typography class `overline`; separators
  in dark mode are slightly stronger, the table body has no column lines.
- **Commit and rollback (#30259):** after a successful database commit, `Committable.rollback()` is no
  longer called; an exception of a synchronous `UpdateListener` is logged and no longer thrown out of
  `commit()`. Flags in own committables that guarded against this are no longer needed.
- **Access check for another person (#30269):** `ModelAccessRights.isAllowed(Person, ...)` decides by
  the given person, not by the bypass or admin state of the current context. A workaround switching the
  identity before the check is no longer needed; code that relied on the old bypass now gets `false`.
- **Hidden instead of disabled (#30025):** React commands and units for which the user holds the
  needed role on no object are hidden instead of disabled.
- **Executability rules first (#30211):** configured `<executability>` rules of a React command now
  apply before `<access>` and the built-in rules (e.g. of `<delete-object>`); `clique="delete"` makes a
  command destructive (red confirmation in `<confirm>`).
- **Busy indicator (#30237):** a command unanswered for longer than `busy-delay` (default 500 ms,
  `ViewConfig`) blocks the page with a busy indicator. Own React controls sending requests next to the
  input (suggestions, lazy loading) should set the option `background` (`BACKGROUND_COMMAND`), otherwise
  the page blocks during these requests.
- **Slots (#30045):** a `<slot-content>` that reaches no `<slot>` is logged as warning (diagnosis
  `unrouted`); set `optional="true"` for content that has a slot only in some shells. Panes of split
  panels and sidebar entries have slot positions of their own, which can change the routing.
- **`/agent-api` without login (#30125):** a request without valid credential gets `401` with JSON
  instead of a redirect to the login page.
- **Session ID on login (#30069):** `SessionService.loginUser` changes the session ID on every login
  with an existing HTTP session (protection against session fixation), also for an external login.
- **Generic OpenID login (#30179):** the button of the generic OpenID client shows `tl_pac_label`
  (by default a generic "single sign-on" label instead of the technical client name). An existing `useSSO`
  application may add `pac4j-generic.xml` to its `metaConf.txt` to have the login on the development
  server too (optional).

---

## 8. Verification checklist

1. `mvn clean install` of the application (with tests if possible).
2. Start the application against a copy of the production database first: the migrations of #30031
   (unique link tables), #30079 (comments deleted) and #30167 (column handling) run at startup.
3. Read the startup and migration log: no configuration errors (`blocked-by-default`, `group-mapper`,
   `user-mapping` attributes, tool access policy with unknown models), no errors of
   `Ticket_30031_unique_link_tables`.
4. Log in, open `/view/` (shell overlay, #30044) and every React view; open the classic layouts with
   scheduler, self-service and search.
5. Check styling of selected rows, nodes and items in own stylesheets / MUI themes (#30000) and row,
   cell and display colours (#30256).
6. Check access: users without admin rights see the expected units and commands; role rules over
   contacts (#30096) and security parents of structured types (#30079) give the expected rights.
7. Report to the user every step that was skipped, needed a decision, or touches data (deleted
   comments, removed duplicate links, changed access rights).
