Tags

Tags give the ability to mark specific points in history as being important
  • 1.0.1a2

    Release: Release 1.0.1a2
    # 1.0.1a2 - Alpha 2
    
    Second alpha of the 1.0.1 line, additive over 1.0.1a1 apart from the two behaviour changes
    noted below. It keeps pooled connections alive across idle timeouts, adds the `ecm.portfolio`
    namespace, credential and password-rule checks in `ecm.security`, variant reading and
    activation in `ecm.dms`, and fixes a set of defects around object rights, table fields,
    register targets and cancelled notification teardowns.
    
    Added:
    - Keepalive for the connection pool: `keepalive_interval` on `SyncPoolClient` / `AsyncPoolClient` probes idle pooled connections with `krn.CheckServerConnection`, so NAT, firewall and load-balancer idle timeouts no longer drop connections silently; failures close the connection, are logged at DEBUG only and are counted separately in `ConnectionStats.keepalive_count` / `last_keepalive_at` (#15859)
    - Explicit pool shutdown: `SyncPoolClient.close()` and `AsyncPoolClient.aclose()`, plus (async) context-manager support on both pool clients; garbage collection stays a fallback rather than the documented path
    - `ecm.portfolio` covers enaio portfolios ("Mappen") with `search()`, `get()`, `user_favorites()`, `create()`, `delete()`, `delete_for()`, `add_objects()`, `remove_objects()` and `clear_objects()`, returning frozen `ECMPortfolio` / `ECMPortfolioObject` data classes; this replaces the former `portfolio` extra and its separate `ecmind-blue-client-portfolio` package
    - `ecm.security.check_user_account()` checks whether a user can log in (`krn.CheckUserAccount`) and reports existence, password match, lock state and remaining password validity as `ECMUserAccountCheck` / `ECMUserAccountStatus`; needs the `SERVER_SWITCH_JOB_CONTEXT` system role and counts failed attempts towards the account lockout like a real login
    - `ecm.security.check_password_complexity()` checks a password against the server's `Login\PwdComplexity` rule (`krn.VerifyPassword`); despite the job name this is not a credential check
    - `ecm.dms.set_active_variant()` switches the active variant of an existing W-document and resolves the previously active variant itself; activating the already-active variant performs no job at all
    - `obj.system.active_variant` returns the active variant of a document loaded with `variants=True` without a server call
    - `ecm.dms.variants()` and `ecm.dms.active_variant()` read a document's variant tree from an object ID alone, and `ECMModelDocumentVariant.walk()` flattens a node with its descendants
    - Document variants carry their nesting depth as `level`, on `ECMModelDocumentVariant` and `DmsContentDocumentVariant`
    - Result code `-1042218022` ("a system role required for this job is missing") is translated into `ECMAccessDeniedException` instead of a raw `BlueException`
    
    Fixed:
    - `obj.system.rights` reports the actual permissions instead of granting everything: the five right attributes were evaluated with `bool()` on the raw string, and `bool("0")` is `True`, so a rights-aware user interface could never hide or lock anything. The values are compared now, an unevaluated right reads as `None`, `DmsContentObjectRights.evaluated` tells the cases apart, and an unevaluated `<Rights>` element no longer populates the model (#15860)
    - `.rights()` and `rights=True` request the insert quotas (`ObjectInserts=1`) as well, without which `ECMModelRights.insert` would have been permanently `False` (#15860)
    - `DmsContentObjectRowField.system` shared the same `bool()` cast and reported every LOL table column as a system column
    - `ecm.dms.upsert()` can empty a table field with `.replace_table_fields(True)` and no longer clears one by accident: `ECMTableList` records whether the caller touched a list, a touched but empty list is emitted as an empty `<TableField/>`, and value-less rows no longer clear a table. Behaviour change: emptying a table field without deciding on `.replace_table_fields()` raises `ValueError` instead of sending a request that silently changes nothing (#15842)
    - `ecm.dms.update()` can empty a table field again: the emptied table was detected and `REPLACETABLEFIELDS=1` was set, but the `<TableField>` element was skipped, so the server had nothing to replace the rows with and reported success (#15826)
    - A cancelled `NotificationSubscription.stop()` closes the callback channel again: teardown now frees channel, receiver and hub state synchronously before the first await, the best-effort remainder is shielded from the caller's cancellation, a hub that lost its receiver raises `ECMWrongStateException` instead of being reused, `open_callback` closes its socket on any `BaseException`, and channel generations keep an outlived receiver from reading a reopened channel. The synchronous hub had the same window without any cancellation and gets the same treatment (#15827)
    - `ecm.dms.move()` and `ecm.dms.copy()` file into the target register again instead of the folder: the server identifies the register by the pair `register_id` + `register_type`, and the type is now taken from the argument, the instance, the class, or one `dms.GetObjectTypeByID` lookup as a last resort (#15838)
    - `AsyncPoolClient`'s cleanup task could never fire, because the coroutine held the pool through a bound method and the weak reference it polled never became `None`; idle connections stayed open until the event loop was torn down
    - `DmsContentDocumentVariant.is_active` evaluated the raw attribute with `bool()`, so inactive variants sent as `is_active="0"` reported `True`
    - `ECMModelDocumentVariant.doc_parent` is `None` for the root variant again: a 12.0 server sends `0` where the XSD documents `-1`
    - Breaking: `ECMUserAttributes.last_modified` is replaced by `profile_changed: bool`. The `geaendert` attribute is a `0`/`1` profile-changed flag, not a Unix timestamp, so it parsed as `1970-01-01 00:00:01+00:00` whenever it was set. The descriptions of `never_expire` (password expiry, not account expiry) and `login_count` (server-side count of login attempts) were corrected in the same pass
    
    Full changelog: https://gitlab.ecmind.ch/open/ecmind_blue_client/-/compare/1.0.1a1...1.0.1a2
  • 1.0.1a1

    Release: Release 1.0.1a1
    # 1.0.1a1 - Alpha 1
    
    First alpha of the 1.0.1 line. It makes every timestamp independent of the client's timezone,
    hardens the RPC base layer against short reads, leaked descriptors and orphaned temporary files,
    and adds an explicit way to declare the timezone of the enaio installation. The public API of
    1.0.0 is unchanged apart from the additive capabilities below; the timezone handling is the one
    behaviour change, and it only takes effect once configured.
    
    Added:
    - set_server_timezone() / get_server_timezone() declare the timezone of the enaio installation, plus the ECMIND_BLUE_SERVER_TIMEZONE environment variable for deployments that would rather not touch application code
    - epoch_to_server_time(), server_time_to_epoch() and to_server_time() expose the conversions for callers that process timestamps themselves
    - result_as_file() on the query builders requests the hit list as a response file instead of the inline XML parameter; off by default, intended for single pages above file_cache_byte_limit
    - stream_lol_rows() / stream_hol_objects() in ecmind_blue_client.ecm.parser read a DMSContent response incrementally instead of building the whole DOM
    - tzdata as a runtime dependency on Windows: Windows ships no IANA time zone database, so zoneinfo cannot resolve a name like Europe/Zurich there
    
    Changed:
    - Every epoch conversion resolves through the installation timezone instead of the host's: DATETIME index fields, the Created / Modified base parameters, system.deleted_at, the concurrency_timestamp used for optimistic locking, workflow datetime variables and JobParameterTypes.DATE_TIME. Left unset the host timezone still applies, so a deployment that already runs in the installation's timezone is unaffected. Read and write move together because concurrency_timestamp is a round trip of OBJECT_MODIFYTIME
    - dms.GetResultList and dms.GetObjectDetails return their payload inline as the XML parameter instead of as a response file
    - The paging stream() / execute() methods of select() and select_lol() walk each response page incrementally; the hierarchical with_children() / with_parents() path keeps the DOM parser
    - Note that 1.0.0 advertised no runtime dependencies; that now holds on Linux only
    
    Fixed:
    - The async job path reads fixed-size response fields with StreamReader.readexactly again, so a cancelled read consumes nothing instead of leaving the connection desynchronised
    - Temporary files of a response are deleted on every error path of sync_call_job / async_call_job; a truncated or digest-mismatched response with a large attachment previously orphaned them permanently
    - sync_connect closes its socket in every failure branch; a retry loop against an unreachable server leaked one descriptor per attempt and ran towards EMFILE
    - value_offset in a job parameter description counts the UTF-8 bytes of the parameter name instead of its characters, so a non-ASCII name no longer points into the middle of the name
    - BIGINT parameters can be sent instead of raising "Bigint is currently not supported"
    - async_call_job defaults parameters to None like sync_call_job does, so code ported between the variants no longer raises TypeError
    - The fixed-size fields of a job response are read completely rather than in a single recv that may return less
    - A connection closed in the middle of a job response raises BlueProtocolException instead of failing later with a misleading error
    - _sync_send_request sends with sendall instead of send, which could transmit only part of the request and leave the server waiting
    - The unreachable "invalid parameter type" branch in deserialize_job_parameters reports the type instead of raising TypeError
    - bind_sql_params no longer interprets a percent sign inside string literals, quoted identifiers or comments as a placeholder
    
    Full changelog: https://gitlab.ecmind.ch/open/ecmind_blue_client/-/compare/1.0.0...1.0.1a1
  • 1.0.0

    Release: Release 1.0.0
    ecmind_blue_client 1.0.0
    
    First stable release of the typed `ecmind_blue_client.ecm` API, available in full as
    sync and async variants.
    
    - Namespaced API: `ecm.dms`, `ecm.security`, `ecm.system`, `ecm.workflow`, `ecm.db`,
      replacing the client-level methods (`lol_query`, `xml_import`, `get_object_details`, ...)
      and the former `manage`, `objdef` and `workflow` companion packages.
    - Typed model classes for folders, registers and documents, generated from a live server
      or an `asobjdef.xml` via `ecm-generate-models`, plus a chainable query builder for HOL
      and LOL with table-field conditions, full text and hierarchy queries.
    - Partial updates, upsert, variants, move/copy, check-in/check-out, and chunked streaming
      of large file attachments.
    - Rewritten connection pool: TLS by default, one shared session per server, AES password
      scheme without the 62-character limit, per-server connection checks.
    - Server push notifications over a dedicated callback channel.
    - Any server job remains directly callable through `ecm.execute()`.
    
    Requires Python 3.12. No runtime dependencies. `TcpClient` / `TcpPoolClient` and the `tcp`
    extra are deprecated; the `portfolio` extra has not been ported yet.
    
    Documentation and migration guide: https://ecmind-blue-client.docs.ecmind.ch
  • 1.0.0rc9

    Release: Release 1.0.0rc9
    # 1.0.0rc9 - Release Candidate 9
    
    Ninth release candidate of the 1.0.0 line, additive over 1.0.0rc8. It fixes system field
    handling in DMSQuery result field lists, enabling sorting and selection on Model.system.*
    fields in the typed query builders (for example keyset pagination over the object ID).
    The public API of 1.0.0rc8 is unchanged.
    
    Fixed:
    - Result field lists emit system="1" for system field names passed as plain strings, so order_by(Model.system.id.DESC) and fields(Model.system.id) work on HOL and LOL builders (sync and async) instead of failing with server error -1031 (#15650)
    - DmsQueryFields.field() merges repeated calls for the same field into one Field element instead of emitting duplicates, so a field that is both selected and sorted on produces a single entry (#15650)
    
    Full changelog: https://gitlab.ecmind.ch/open/ecmind_blue_client/-/compare/1.0.0rc8...1.0.0rc9
  • 1.0.0rc8

    Release: Release 1.0.0rc8
    # 1.0.0rc8 - Release Candidate 8
    
    Eighth release candidate of the 1.0.0 line, additive over 1.0.0rc7. It adds server-push
    notifications (a low-level callback channel and the high-level `ecm.notification` namespace),
    makes the connection pool keep one shared session per server, and fixes two model-generator
    defects. The public API of 1.0.0rc7 is unchanged apart from the additive capabilities below.
    
    Added:
    - ecm.notification namespace: typed server-push notifications (job_calls / messages / listen / send_message)
    - Notification callback channel in the low-level RPC layer (sync/async open_callback + callback_next, protocol v50 with SHA-1 digest)
    - ecm-callback-listen console script to inspect the server's push stream from the shell
    - connection_for(hostname, port) on the pool clients to borrow a connection to a specific server
    
    Changed:
    - Connection pool keeps one shared session per server (attach instead of repeated login)
    - create_os_event() / update_os_event() send the script code as UTF-8 with a BOM instead of cp1252
    
    Fixed:
    - ecm-generate-models emits the stored key (not the display text) into flat-list catalog enums
    - ecm-generate-models sanitizes list-entry keys to valid Python identifiers
    
    Full changelog: https://gitlab.ecmind.ch/open/ecmind_blue_client/-/compare/1.0.0rc7...1.0.0rc8
  • 1.0.0rc7

    Release: Release 1.0.0rc7
    # 1.0.0rc7 - Release Candidate 7
    
    Seventh release candidate of the 1.0.0 line, additive over 1.0.0rc6.
    
    Added:
    - ecm.dms.files_streaming(): chunked attachment download without local buffering (#15486)
    - Modern AES login password scheme, lifting the 62-character limit (#15561)
    - ecm.system OsEvent management: get_os_events / create / update / delete_os_event
    - ecm.system.refresh_server_events() to reload the OsEvent cache (#15555)
    - execute_and_get() on the upsert builder
    
    Fixed:
    - ecm-generate-models drops flat-list display text from enum values
    - password_encrypt() RecursionError on passwords over 62 characters (#15561)
    - document_stream() no longer caps offset/length at 2 GiB (#15486)
    - update_and_get() overload typing without a type-ignore (#15486)
    
    Full changelog: https://gitlab.ecmind.ch/open/ecmind_blue_client/-/compare/1.0.0rc6...1.0.0rc7
  • 1.0.0rc6

    9753ce99 · Updated version and date ·
    Release: Release 1.0.0rc6
    # 1.0.0rc6 - Release Candidate 6
    
    Sixth release candidate of the `1.0.0` line. It adds document search-flag querying,
    archive/lock status properties on document instances, and three new system methods
    (`get_icons`, `jobs`, `check_connections`), and fixes the `build` CI job. The public
    API of `1.0.0rc5` is unchanged apart from the additive capabilities below.
    
    ## Added
    
    - **Query documents by `OBJECT_SEARCHFLAGS` state (#14491).** The `ObjectSearchFlags`
      enum existed but could not be used: the only condition entry point emitted `system="0"`,
      so the server ignored the bitmask. Document models now expose boolean search-flag
      properties on their `system` namespace (`Model.system.in_register`, `.signed_current`,
      `.without_pages`, `.archivable`, ...) usable directly in `where()`, e.g.
      `ecm.dms.select(Document).where(Document.system.in_register == True).execute()`.
      Multiple flag conditions collapse into one correct `<FieldCondition
      internal_name="OBJECT_SEARCHFLAGS" system="1">`. Paired flags (`archivable`,
      `in_register`) also accept `== False`.
    
    - **Archive-status and lock properties on loaded document instances (#14491).**
      `OBJECT_FLAGS` (1102) and `OBJECT_LOCKUSER` (1116) are now part of the document default
      system fields, so `ECMDocumentModel` instances expose decoded boolean properties without
      any extra query flag: `obj.system.archived`, `.archivable`, `.not_archivable`,
      `.page_error`, `.without_pages`, `.reference`, `.external_archived` (from the
      `OBJECT_FLAGS` status code) and `.locked` (from `OBJECT_LOCKUSER`), plus the raw
      `.flags`. The query-only search-flag properties (`multi_location`, `has_variants`,
      `signed_current`, `signed_former`) remain usable only inside `where(...)`.
    
    - **`ecm.system.get_icons()` fetches object/type icons by ID.** Wraps the `cnv.GetIcons`
      server job and returns the GIF images for the given icon IDs as a `dict[int, bytes]`
      (icon ID to raw GIF bytes). Icon IDs come from the object definition (`icon_id` per
      object type) or from `document.system.file_properties.iconid`. Multiple IDs are fetched
      in a single round-trip; unknown IDs are silently omitted; passing no IDs returns `{}`
      without contacting the server. Sync and async.
    
    - **`ecm.check_connections()` probes every configured pool server individually (#15467).**
      This root-level method opens a throwaway connection to each server in the pool
      configuration (TCP/TLS, login, `krn.GetServerInfoEx`), reports reachability,
      authentication state and server info per server, then closes it again. Probe connections
      never enter the pool and never affect pool statistics; one unreachable server does not
      abort the others, failures are recorded in the result's `error` field instead of being
      raised. Returns `list[ECMServerConnectionCheck]` (sync and async).
    
    - **`ecm.system.jobs()` lists all server jobs grouped by namespace.** Combines
      `krn.EnumNameSpaces` and one `krn.EnumJobs` call per engine into a single
      `dict[str, list[str]]`, keyed by namespace short label (e.g. `"krn"`) with the
      namespace's jobs as values, each including the namespace prefix. Dict keys and job lists
      are sorted; the async variant runs the per-engine calls concurrently (sync and async).
    
    ## Fixed
    
    - **The `build` CI job failed at `uv build` (#15468).** A newer `uv` refuses to build when
      its cache lives inside the build source tree, and the shared GitLab cache restored
      `.cache/uv` back into the project directory. The `build` job now points `UV_CACHE_DIR`
      outside the source and skips the shared cache, so the package builds again. The test job
      keeps its in-project cache for reuse between pipelines.
    
    **Full changelog:** https://gitlab.ecmind.ch/open/ecmind_blue_client/-/compare/1.0.0rc5...1.0.0rc6
  • 1.0.0rc5

    Release: Release 1.0.0rc5
    # 1.0.0rc5 - Release Candidate 5
    
    Fifth release candidate of the `1.0.0` line. It fixes a model-generator defect that
    silently dropped data-bearing fields displayed with the GROUP control. The public API of
    `1.0.0rc4` is unchanged.
    
    ## Fixed
    
    - **`ecm-generate-models` dropped data-bearing GROUP-control fields.** enaio uses the GROUP
      control (`dt='G'`) both for visual containers and for real index fields that have their own
      data type and database column. The generator skipped every GROUP control, so such fields
      (e.g. `PostDoc`'s `hash`, `group`, `user`) were silently missing from the generated model
      class. GROUP-control fields that carry a data type are now emitted as `ECMField`
      declarations; purely visual GROUP boxes (no data type, or static) stay excluded. Present
      since `1.0.0a1`; regenerate model files to pick up the affected fields.
    
    **Full changelog:** https://gitlab.ecmind.ch/open/ecmind_blue_client/-/compare/1.0.0rc4...1.0.0rc5
  • 1.0.0rc4

    Release: Release 1.0.0rc4
    # 1.0.0rc4 — Release Candidate 4
    
    Fourth release candidate of the `1.0.0` line. It adds class-level queryable system
    fields and group-management write methods, and fixes license handling on enaio 12,
    field clearing on update and a SQL-whitespace edge case. The public API of `1.0.0rc3`
    is unchanged apart from the additive capabilities below.
    
    ## Added
    
    - **Class-level `Model.system.<field>` as queryable fields.** `system` is now
      access-aware: instance access (`obj.system.id`) still returns the loaded value, while
      class access (`Model.system.id`) returns an `ECMField` usable in `where()` /
      `order_by()`. This enables cross-type filtering on a parent via the same dotted path,
      e.g. all documents inside a known folder via
      `select(Doc).where(Folder.system.id == folder.system.id)`. Exposed for every system
      field with a single queryable backing column (`id`, `owner_guid`, `creator`,
      `creation_date`, `last_modifier`, `last_modified`, `creation_time`, `deleted_at`;
      register/document location fields; and the queryable subset of `system.base_params`
      and `system.file_properties`). Aggregate values without a backing column (`rights`,
      `name`, `is_modified`) remain instance-only. Verified against a live server.
    - **Group-management write methods on `ecm.security` (#15...).** `create_group()`,
      `update_group()`, `delete_group()` and `empty_group()` wrap `mng.CreateGroup`,
      `mng.SetGroupAttributes`, `mng.DeleteGroup` and `mng.EmptyGroup`. `create_group()`
      returns an `ECMGroup` with the server-assigned `id` and `guid`; `update_group()` takes
      a (modified) `ECMGroup`; `delete_group()` / `empty_group()` accept either an `ECMGroup`
      or a group name. A group can only be deleted once empty. Sync and async.
    
    ## Fixed
    
    - **License jobs no longer raise a hard error on enaio 12+.** `check_license()` and
      `module_info()` now pass `Flags=1` to the `LIC_CHECKLICENSE` and `LIC_LICGETMODULEINFO`
      server jobs; since enaio version 12 these return a hard error with the previous
      `Flags=0`. As a consequence `module_info()` for an unknown module now returns a result
      with a `?` license-type marker instead of failing; this is detected and reported as the
      documented `ECMNotFoundException` (sync and async).
    - **Fields could not be cleared on update (#15434).** Setting a loaded field to `None`
      was correctly detected as a change, but `_build_update_xml` skipped `None`-valued
      fields, so the reset was never sent and the server kept the old value. A field changed
      to `None` is now emitted with `field_function="NULL"` so the server actually clears it
      (sync and async). `insert()` still omits `None` fields.
    - **`ecm.db.select()` raised an opaque `IndexError` on leading whitespace (#15431).** A
      statement beginning with whitespace (e.g. a leading newline from a multi-line literal)
      made `ado.ExecuteSQL` return no result set while still reporting success, tripping
      `files[0]`. The bound command is now left-trimmed before being sent (sync and async).
    
    **Full changelog:** https://gitlab.ecmind.ch/open/ecmind_blue_client/-/compare/1.0.0rc3...1.0.0rc4
  • 1.0.0rc3

    Release: Release 1.0.0rc3
    # 1.0.0rc3 — Release Candidate 3
    
    Third release candidate of the `1.0.0` line. It adds table-field column conditions to
    the query builder and fixes enum and workflow-model handling. The public API of
    `1.0.0rc2` is unchanged apart from the additive query capability below.
    
    ## Added
    
    - **Table-field column conditions in the query builder (#15428).** Conditions can now
      target columns of a sub-table / multi-field (e.g. `Invoice.Positions.ArticleNo == "A-100"`),
      emitting `<TableCondition>` / `<TableColumn>` instead of a flat field condition. Restrict a
      condition to a row via `Invoice.Positions[3].Quantity == 5` (alias `.row(3)`). Works in
      `select` and `select_lol` (sync and async) with all comparison and collection operators.
    
    ## Fixed
    
    - **Enum (catalog) field values were serialized as their Python repr (#15427).** Passing a
      generated `(str, Enum)` / `IntEnum` member to `insert` / `update` / `upsert` emitted
      `Class.MEMBER` instead of the catalog value, which the server rejected. Enum members are
      now unwrapped to their value before conversion.
    - **Workflow start inputs were under-detected.** Inputs were read from `INOUT` parameters
      across the whole model (including activities) and `IN` was ignored. They are now read from
      the `<WorkflowProcess>` interface only, counting both `IN` and `INOUT`; the standard ad-hoc
      workflow, for example, previously reported 2 of its 5 input variables.
    - **List-of-record workflow variables lost their type and members.** Variables typed as a
      list of records are now correctly typed as `list[...Record]` with their members.
    
    ## Changed
    
    - **Generated workflow-model class names derive from the stable workflow display name**
      (e.g. `Standard_Ad_hoc_WorkflowModel` instead of `Ad_hoc_Version_3_0_5WorkflowModel`), so
      they no longer change when the workflow version increases.
    
    ## Documentation
    
    - New **FastAPI integration** and **workflow-start** guides (with matching skills). German
      docs use "Schrank"; skill documentation links now point to the hosted Antora site.
    
    **Full changelog:** https://gitlab.ecmind.ch/open/ecmind_blue_client/-/compare/1.0.0rc2...1.0.0rc3
  • 1.0.0rc2

    Release: Release 1.0.0rc2
    # 1.0.0rc2 — Release Candidate 2
    
    Second release candidate of the `1.0.0` line. It adds steerable table-field
    replacement and fixes two object/XML handling defects, one of which affected a
    production environment. The public API of `1.0.0rc1` is unchanged apart from the
    additive parameters below.
    
    ## Added
    
    - **Steerable table-field replacement on `upsert` and `update` (#15422).** New
      `replace_table_fields(value=True)` fluent method on the upsert builder, and a
      keyword-only `replace_table_fields: bool | None` parameter on `ecm.dms.update()`
      and `ecm.dms.update_and_get()` (sync and async). `None` (default) keeps the
      automatic detection, `True` forces `REPLACETABLEFIELDS=1`, `False` suppresses it.
    
    ## Fixed
    
    - **Invalid upsert XML when a search section was used (#15424).** The `<Search>`
      element was placed after `<Fields>`, but the schema requires it first; the server
      rejected such requests (`Element 'Search' is unexpected …`). `<Search>` is now
      emitted first, so every `upsert(...).search(...)` call produces valid XML.
    - **Object definition dropped fields from unnamed PageControl tabs.** Tabs with a
      blank internal name collapsed onto one entry and lost their fields. Pages now fall
      back to their unique `page_id`, restoring the missing fields in
      `ecm.system.definition()`, `ecm.dms.model_by_name()` and `ecm-generate-models`.
    
    **Full changelog:** https://gitlab.ecmind.ch/open/ecmind_blue_client/-/compare/1.0.0rc1...1.0.0rc2
  • 1.0.0rc1

    Release: Release 1.0.0rc1
    First release candidate of the `1.0.0` line. There are **no functional changes
    to the library API** since `1.0.0a9`; this candidate stabilises the release
    tooling and test suite.
    
    - **Automatic GitLab Release on tags.** Tag pipelines now create a GitLab
      Release whose description is taken from this tag's message. The built library
      (wheel + sdist) and the rendered documentation are uploaded to the project's
      generic Package Registry and linked as permanent release assets, alongside a
      link to the matching PyPI project page. The rendered Antora site is also
      bundled as `docs-<tag>.zip`.
    
    - **Workflow process-list integration test no longer assumes the creator is the
      inbox owner.** `ecm.workflow.process_list_by_user()` returns the processes in
      a user's inbox, and an inbox item may have been created by any user. The
      `test_process_has_creation_block` test (sync and async) now only asserts that
      the `creation` block is populated. No change to library behaviour.
    
    **Full changelog:** https://gitlab.ecmind.ch/open/ecmind_blue_client/-/compare/1.0.0a9...1.0.0rc1
  • 1.0.0a9

    2c4405ab · Release 1.0.0a9 ·
    Release: 1.0.0a9
  • 1.0.0a8

    fe61107c · Release 1.0.0a8 ·
  • 1.0.0a7

    61c9ba1f · Release 1.0.0a7 ·
  • 1.0.0a6

    ### Added
    
    - **`ecm.workflow.process_list_by_user()`**: New sync and async method wrapping
      the server job `wfm.AdminGetProcessListByUser`. Returns a list of typed
      `ECMProcess` objects, each containing an `ECMProcessCreation` block and a
      tuple of `ECMProcessActivity` instances with parsed timestamps and a
      convenient `is_personalised` flag (equivalent to `Activity.State & 128`).
      The `user` argument expects the workflow-organisation user object — not the
      security user GUID.
    
    - **Full-text search on the typed model query builders**: New `.fulltext(term, **engine_options)`
      chain method on `ecm.dms.select()` (HOL) and `ecm.dms.select_lol()` (LOL), available in both
      sync and async variants. Adds a `<Fulltext>` condition to the queried object type's
      `<ConditionObject>` and accepts optional RetrievalWare engine attributes (`mode`,
      `expansion_level`, `fuzzy_spell_half_words`, `fuzzy_spell_threshold`, `max_fuzzy_spell`,
      `max_reg_expr`, `warn_max_reg_expr`, `word_expansion_limit`). Requires a configured
      RetrievalWare search engine on the enaio® server. The archive-wide `<FulltextQuery>` flavour
      (hits across multiple object types in one request) remains HOL-exclusive at the low-level
      `DmsQueryBuilder.fulltext_query()` API and is intentionally not exposed on the typed
      single-model builders.
  • 1.0.0a5

    3612af07 · Add Changelog ·
    ## 1.0.0a5
    
    ### Added
    - `ECMDocumentModelSystem.system_id` (`OBJECT_SYSTEMID`): ID of the external archive system holding the referenced file; now also loaded on `get()`.
    - Read-only enforcement for model fields via the new `ECMField(read_only=...)` parameter (`"always"` / `"init"` / `"arch"`), checked on `insert()`, `insert_variant()`, `update()` and `upsert()` (sync and async). Disable per call with `check_read_only=False`.
    
    ### Changed
    - `generate_ecm_models` now derives `read_only` from the ECM flags (`readonly` → `"always"`, `readonly_after_initialization` → `"init"`, `readonly_after_archiving` → `"arch"`) and emits it on the generated `ECMField(...)` line.
    - Clarified `foreign_id` documentation, including the *green arrow* special case.
    
    ### Fixed
    - **#15222** Model feature: `update()` failed for fields flagged "read-only after initialization". A `mandatory` + `read_only="always"` field is no longer generated as a required constructor argument.
  • 1.0.0a4

    0658eb72 · Update Readme ·
    ## Release Notes — since 1.0.0a3
    
    ### New features
    
    - **Skills bundle for AI assistants** — collection of compact, LLM-optimised skill descriptions for every public ECM operation. Auto-loaded by Claude Code / Cline / Cursor via description-based invocation. Downloadable as `/skills.zip` from the docs site.
    - **`ecm.security` user lifecycle** — `create_user()`, `update_user()`, `delete_user()`, `add_user_to_group()`, `remove_user_from_group()`, plus full sync + async coverage and DE/EN documentation.
    - **`ecm.security.export_security_system()`** — typed `ECMSecuritySystemExport` wrapping `mng.ExportSecuritySystem`. Group-level security clauses are mapped to their object rights; `ECMObjectRight` (R/W/D/X/U) and `ECMAnnotationRight` (G/P) are decoded as `IntFlag`.
    - **`ecm.system.info()`** — process-local snapshot of pool connection stats and auto-fetched server info. No RPC; cheap to call from dashboards or health checks.
    - **Pool connection statistics** — `pool.connection_stats()` returns a `list[ConnectionStats]` (host, port, server_info, connected_at, last_call_at, call_count, stable `connection_id`).
    - **`RpcServerInfo`** — fetched once per pool connection from `krn.GetServerInfoEx` and exposed on `RpcConnection.server_info`. Tolerant parser: partial server responses never break the connection.
    - **Coverage in CI** — every `unittests` run now emits Cobertura + JUnit reports and feeds the GitLab coverage badge.
    
    ### Improvements
    
    - **Codebase-wide docstring refactor** — Google-style docstrings (Args/Returns/Raises/Example) across the public API and across the test suite.
    - **`pyproject.toml` cleanup** — removed dead `[tool.uv.workspace]` / `[tool.uv.sources]` declarations, dropped redundant `setuptools-scm` and unused `pytest-xdist` from dev extras, added `pyright`. The `TcpClient` extra alias is kept and now documented as a backwards-compatibility shim.
    - **`.gitlab-ci.yml` overhaul** — shared `default.before_script` with `uv sync --frozen` (lockfile drift now fails the pipeline); `build_docs` moved into the `build` stage and now zips `skills/ecmind-blue-client` into `public/skills.zip`; `deploy_pypi_alpha` renamed to `deploy_pypi_prerelease` (regex covers `a`/`b`/`rc`); redundant `when: always` removed from deploy rules.
    - **Documentation** — new "Skills for AI assistants" / "Skills für KI-Assistenten" section on the DE + EN index pages with direct download link.
    
    ### Fixed
    
    - **`uv build` regression** introduced by the dev-extra cleanup — `--no-build-isolation` dropped from the `build` job so build dependencies come from `[build-system].requires` again.
    - Test assertions aligned with current behaviour for XML self-closing tags and SQL-quote escaping.
  • 1.0.0a3

    ## Release Notes — since 1.0.0a2
    
    ### New features
    
    - **Document variants** (dc79def) — `ecm.dms.insert_variant(...)` and `insert_variant_and_get(...)` (sync + async) for creating new variants of an existing document. The internal XML builder gained a `variant_parent_id` parameter; the variant inherits `folder_id`/`register_id` from its parent. New docs: `insert_variant`, `insert_variant_and_get`.
    - **Generic model from type name** (f104f2c) — `ecm.dms.model_by_name("…")` resolves an internal object-type name via `ecm.system.definition()` and returns the matching dynamic subclass (`ECMFolderModel` / `ECMRegisterModel` / `ECMDocumentModel`). Usable directly in `select(...)`. New doc: `model_by_name`.
    - **Default system fields inherited** (24beaaf) — `ECMRegisterModel` and `ECMDocumentModel` now append their specific `SystemFields` to `_ECMModelBase._ecm_system_fields_` instead of overriding them. Breaking for code that relied on the base fields being absent.
    
    ### Improvements
    
    - **`tcp` extra is optional again** (68fad5f) — `Client`, `Job`, `Param`, `QueryConditionField`, `QueryConditionGroup`, `QueryResultField` are lazily re-exported in `ecmind_blue_client/__init__.py` via PEP 562 `__getattr__`. The ECM abstraction (`SyncPoolClient` / `AsyncPoolClient`) can now be used without installing `XmlElement`.
    - **Pyright config** (94736bc) — `build`, `dist`, `__pycache__`, `.venv` excluded from the scan.
    
    ### Cleanup
    
    - **Expanded tests** — new suites for variants (`test_ecm_dms_variant_sync/async/unit.py`) and `model_by_name` (`test_ecm_model_sync/async.py`); minor cleanups in the async DMS tests.
  • 1.0.0a2

    1c880159 · Add std.GetDocumentStream ·
    ## Changelog since tag `1.0.0a1`
    
    ### New Features
    
    **DMS**
    - `std.GetDocumentStream` – partial reading of document file ranges (1c88015)
    - `dms.SetUserData` / `dms.GetUserData` – user-scoped data storage (86c37c1)
    - `dms.CheckOut`, `dms.UndoCheckOut`, `dms.CheckIn` including document history (693133d, b7da1e0)
    - `dms.CheckPermission` (3cc2bad)
    
    **Workflow (wfm)**
    - Full Workflow API added (e2fcdad)
    - Workflow organisations (1bcc01f)
    - `wfm.SetSubstitutes` (cbd138d)
    - `wfm.ConfigUserAbsence` (a7809df)
    - `wfm.SaveOrganisation` (d067fd8)
    
    **License & Kernel**
    - `lic.CheckLicense` and `lic.LicGetModuleInfo` (2c80f84)
    - `krn.RunScript` and `krn.EmptyJob` (78552c3)
    
    ### Improvements
    - Eliminated unnecessary `get_object_type_by_id` calls (f929ec4)
    - Tests optimised, manual unit tests added (84ab824, e3f637b)
    
    ### Documentation & Infrastructure
    - Model API documentation added (04372aa)
    - Docker container for documentation added (c8405c1, 1e03990, 5589eae, 9ce084a, 340c39c)
    - German documentation as default landing page (9f812b8)