# Znuny and OTRS-compatible service-desk connector ## Boundary Connectors owns the governed endpoint profile, GenericInterface REST transport, credential hand-off, provider discovery, bounded synchronization, external references, mapping diagnostics, health, and recovery evidence. It does not own ticket, helpdesk, or case semantics. A synchronized provider ticket remains an external service-desk ticket; creating or relating a GovOPlaN Ticket, Helpdesk item, or Case is the responsibility of the corresponding optional module. The provider interface is `connectors.external_service_desk@1.0.0`, and the target-tested provider declaration is `connectors.znuny.tickets`. ## Governed configuration and routes Create an active connector definition/configuration with: - `provider`: `znuny`, `otrs`, or `znuny_otrs`; - `protocol`: `generic_interface_rest` (the aliases `rest` and `otrs_generic_interface_rest` are accepted); - an HTTP(S) endpoint that passes the central outbound-request policy; and - an optional scoped Core credential-envelope reference. Znuny GenericInterface routes are configured by each deployment rather than being one universal product API. The profile therefore governs relative search, ticket-read, and optional update paths and their supported methods. Ticket and update paths must contain `{ticket_id}`. An optional absolute HTTP(S) browser URL template may contain `{ticket_id}` or `{ticket_number}`. `search_filters` carries up to 100 deployment-supported, secret-free GenericTicket search criteria such as queue identifiers. It cannot override synchronization bounds, ordering, change cursors, or authentication. Routes cannot change authority or contain credentials. Header authentication is the recommended default. The adapter supports the documented `X-OTRS-Header-UserLogin`, `X-OTRS-Header-Password`, `X-OTRS-Header-SessionID`, and customer-login headers. A legacy credential may declare `auth_mode: body`, but then every affected route must use POST. Secret fields are never placed in a GET URL, persisted projection, diagnostic, or API response. Core also treats these provider headers as redirect-sensitive and removes them before following any cross-origin redirect. Reference configuration examples: - [Znuny GenericTicketConnectorREST example](https://doc.znuny.org/znuny/admin/webservices/examples/GenericTicketConnectorREST/index.html) - [Znuny provider and header authentication](https://doc.znuny.org/znuny-7_1/admin/webservices/provider/) - [Znuny web-service configuration](https://doc.znuny.org/znuny-7_3/admin/webservices/config/index.html) ## Profile policy The integration and authority choices are intentionally separate but bounded: | Integration mode | Allowed authority | Maturity | | --- | --- | --- | | `link` | `linked_reference` | `link` through `search` | | `import` | `external_authoritative`, `external_mirror` | exactly `read` | | `synchronize` | `external_authoritative`, `governed_sync` | exactly `synchronize` | `link` retains stable identity and routing facts and asks the provider not to return articles, attachments, or dynamic fields; any unexpectedly returned content is still discarded. `import` is a deliberate bounded full snapshot and never silently changes to delta synchronization. `synchronize` completes a full reconciliation and then advances to overlap-safe change-time deltas. Queue mappings govern inclusion, an optional opaque target queue reference, and tenant or restricted visibility with ACL tokens. A restricted default requires at least one ACL token. Provider-supplied `GovOPlaNVisibility` and `GovOPlaNACL` fields win when valid. Otherwise a reviewed queue mapping wins, then the profile default. Standard GenericInterface installations do not expose a portable ticket-ACL contract, so every fallback is visible as a diagnostic. Dynamic-field mappings govern source name, optional target name, inclusion, and `string`, `number`, `boolean`, `date`, or `json` conversion. Conversion loss, unreturned configured fields, provider-specific ticket fields, synthesized identities, and truncation are structured diagnostics rather than silent loss. ## Discovery and synchronization Discovery performs a bounded ticket search and records health, API family, route hash, the exact governed configuration revision/hash, product/version evidence, capabilities, maturity, and diagnostics. A changed endpoint, configuration revision, or route map invalidates that evidence: synchronization, and updates fail closed until discovery is repeated, while prior projections are invalidated and Search stays closed until a new full reconciliation verifies them. Integration, route, queue, or dynamic-field mapping changes also reset the cursor and require a full reconciliation. Recognized Znuny or OTRS major versions 6 and later can reach synchronization maturity. An unverified product/version stays at read maturity. An update route adds the technical `publish` capability, but does not override profile authority. Full synchronization first obtains a stable ordered identity set, then reads at most 500 tickets per call. A continued full run stores its offset, identity-set fingerprint, and cumulative high-watermark. If the provider identity set changes mid-run, the cursor is rejected and the operator must restart the full run. A completed full run reconciles local removals and, for `synchronize` mode, transitions to an overlap-safe delta cursor. An explicit `full` request always restarts at the beginning; `auto` continues a committed full cursor or advances a completed delta cursor. A caller-supplied cursor must exactly match the profile's committed cursor, and delta mode cannot bootstrap a profile that has not completed its full synchronization. Delta synchronization reads the bounded candidate set, orders changes by provider change time and ticket id, and suppresses only the exact ticket revisions already observed at the current timestamp boundary. A previously seen ticket that has changed again is therefore not lost. Every run requires a profile-wide idempotency key. An exact replay returns the committed run without contacting the provider; reuse for a different request or changed profile policy is rejected. Operational bounds: - at most 10,000 ticket identities per profile search; - at most 500 ticket reads per API call; - at most 10 MB per provider response; - a 20-second outbound timeout; and - a 4,000-character cursor, including timestamp-boundary identities. Partition larger or unusually bursty providers into queue-scoped profiles by combining provider-side queue `search_filters` with matching queue mappings. A full-run fingerprint conflict requires a restart. A timestamp-boundary overflow requires a narrower partition. These are explicit safety stops, not partial success claims. ## Mapping and attachment policy Each ticket projection retains stable ticket id/number, title, type, queue, target queue reference, state, priority, owner, responsible user, customer user, organization, service, SLA, creation/change times, mapped dynamic fields, articles, attachment metadata, permission source, ACLs, canonical URL, content hash, provider revision, cursor, observation time, and source provenance. Article and attachment references use Core `ExternalObjectReference` values. Article bodies are capped at 200,000 characters. Attachment content is never retained; only stable identity, filename, media type, size, disposition, content id, article/ticket relationship, version, and provenance are mapped. Returned bytes produce an `attachment_content_omitted` diagnostic. ## Search authorization When Search is installed and both desired and discovered maturity permit it, active non-deleted ticket projections are indexed. Search documents carry the current visibility, ACL tokens, external reference, source revision, routing metadata, article text, and bounded dynamic-field keywords. Authorization always fails closed unless all of these remain true: - the requesting principal belongs to the exact tenant; - the principal has `connectors:service_desk:read`; - the profile remains active and search-capable; - the ticket remains active; and - tenant visibility applies or a current account, membership, identity, group, role, function, or scope ACL token intersects. Pausing a profile or changing fallback/queue ACLs updates or removes Search projections immediately. Search also rechecks the current database state for every result, so a delayed index update does not grant access. ## Governed external updates and recovery An external update is allowed only when the profile is active, authority is `governed_sync`, discovery recorded the `publish` capability, and the caller has `connectors:service_desk:update`. The request must include the synchronized provider revision and a new idempotency key. Supported governed fields are title, queue, state, priority, owner, responsible user, and explicitly mapped dynamic fields. Before dispatch, the adapter refetches the ticket and rejects a stale revision. After dispatch, it refetches again and verifies both the changed revision and every requested field value. A transport failure before a conclusive provider response, an unchanged revision, or a requested value that cannot be confirmed becomes `outcome_unknown`. Do not retry with another key. Inspect the provider ticket and reconcile its accepted revision through the Core recovery evidence first. Local database rollback cannot undo a remote provider mutation. ## Administrator verification 1. Create the governed definition/configuration and scoped credential envelope. 2. Create a restricted profile with reviewed routes, queue partitions, dynamic fields, authority, and fallback ACLs. 3. Discover and confirm product/version, maturity, capabilities, and diagnostics. 4. Finish a keyed full run; continue while its cursor kind is `full`. 5. Run a new keyed automatic delta and inspect effects, losses, and health. 6. Verify one allowed and one denied Search principal against a restricted ticket. 7. If governed writes are enabled, update a non-production ticket with its current revision, then verify provider and recovery evidence. 8. Reconcile every `outcome_unknown` run before any retry.