Skip to content
SENS Platform Docs

IOTA snapshot import v1

iota-snapshot-import-v1 is an implemented but deliberately temporary V1 migration contract. It provides one manually triggered snapshot import and is not a synchronization service, background job, scheduled task, or permanent integration.

The feature must be removed immediately after V1 acceptance. Imported device types and devices remain normal SENS records. Historical audit events remain under the normal audit-retention policy.

POST /v1/tenants/{tenantNumber}/imports/iota-snapshot accepts:

{
"baseUrl": "https://commonapi-gesundheit-burgenland.xaas-a0a0.cloud/api",
"username": "entered-for-this-run",
"password": "entered-for-this-run"
}

Only a current platform_admin session may call it. The path tenant is the explicit destination and must exist with active status. Query strings and unknown body fields are rejected. Success and error responses use Cache-Control: no-store.

The base URL must use HTTPS, contain no URL credentials, port, query, or fragment, have path /api, and match the host pattern commonapi-<slug>.xaas-a0a0.cloud. Native HTTP redirects are disabled.

The response contains the destination tenant ID, completion timestamp, raw source counts, created/existing/skipped counts for each entity kind, and at most 200 safe issues. Stable issue codes are:

  • invalid_device_type_code
  • invalid_dev_eui
  • missing_device_type
  • duplicate_source_dev_eui
  • device_exists_in_other_tenant
  • existing_device_mismatch
  • disabled_existing_device_type
  • asset_excluded
  • incomplete_source_record

IOTA authentication, availability, timeout, JSON, and response-shape failures return only a safe DEPENDENCY_FAILURE; upstream bodies and request URLs are not exposed.

The HTTP adapter uses the locally modeled subset of the old IOTA contract:

  1. POST /Authentication with query fields username, password, and generateRefreshToken=false.
  2. GET /Tenant to enumerate every visible source tenant.
  3. GET /DeviceType?tenantId=… for every tenant.
  4. GET /Device?tenantId=… for every tenant.
  5. POST /Connection?tenantId=… with an empty filter for every tenant, used only to identify devices connected to excluded asset pseudo-devices.

Read calls use the returned access token as Bearer authentication. The token is sent only to the validated original origin. Calls are sequentially paginated with page size 100 and stable ID ordering. Each call has a 15-second timeout and the whole source load has a 60-second deadline. Hard limits are 100 tenants, 1,000 device types, 1,000 devices, and 5,000 connections. Exceeding a limit fails before any write. IOTA 204 No Content responses from collection endpoints represent an empty collection and do not fail the snapshot.

The generated contract and implementation in the retired sens-admin repository are not runtime dependencies and were not copied. Only the required response fields are validated locally.

IOTA fieldSENS fieldRule
Device Type codedevice_types.keyPreserve an already valid key; otherwise derive a lowercase readable slug plus SHA-256 hash.
Device Type codedevice_types.nameTrim and preserve the original human-readable value.
Device ediddevices.dev_euiTrim, uppercase, require exactly 16 hexadecimal characters.
Device serialdevices.nameTrim; use normalized DevEUI when empty.
Device typeIddevices.device_type_idResolve within the source tenant to type code, then to the derived SENS type key.
Other source fieldsStatus, subtenant, attributes, and metadata are ignored.

New records use active, metadata {}, and no device organization assignment. The trimmed source type code is the durable import identity. Identical codes from several source tenants merge into one destination type. Invalid-format codes receive a deterministic key whose eight-character hash suffix prevents different source codes with the same readable slug from colliding. Empty or overlong source codes remain invalid and are skipped.

Asset exclusion follows the established IOTA type-code convention: a trimmed code starting with asset or containing _asset followed by a separator is an asset type. These types and their pseudo-devices are excluded. Connections are tenant-scoped; when either endpoint of a connection is an asset pseudo-device, the device at the other endpoint is excluded as being inside that asset. The connection direction does not affect the decision. Unrelated device connections do not exclude either endpoint. Exclusions are reported with asset_excluded and happen before DevEUI validation.

The complete source snapshot is loaded and normalized before the write transaction. The service verifies the destination, then locks its tenant row in the transaction so two imports for the same tenant serialize.

Missing types are created. Existing active types are reused unchanged. Existing disabled types are not reactivated, and dependent source devices are skipped. Devices are checked globally by normalized DevEUI. A device in another tenant is never moved. A device already in the destination remains unchanged; name or type differences produce an issue. Identical source rows for the same DevEUI deduplicate; contradictory rows for that DevEUI are all skipped.

Expected source problems are removed before persistence. Every remaining valid type and device, its device_type.created or device.created audit event, and the final iota_snapshot_import.completed summary event commit together. A database, constraint, or audit failure rolls the whole valid subset back.

Base URL, username, and password are form inputs for one request. There are no IOTA environment variables, Helm values, Kubernetes Secrets, database columns, cookies, or browser-storage entries. The password is cleared from component state after success or failure.

The constrained host pattern, HTTPS-only scheme, same-origin token use, disabled redirects, and bounded calls prevent the route from acting as a general-purpose server-side request primitive. Operators must still allow outbound DNS and HTTPS from platform-api to the selected matching host.

Request bodies are excluded from platform logs and audits. Operational logs contain only correlation ID, destination tenant ID, operation, outcome, and aggregate counters. Prometheus labels never include tenant, URL, username, or DevEUI. Entity audit metadata contains counts only, never credentials or source records.

The optional manual source is not a health or readiness dependency. Its metrics are:

  • sens_iota_snapshot_import_runs_total{outcome}
  • sens_iota_snapshot_import_duration_seconds
  • sens_iota_snapshot_import_entities_total{entity,outcome}

The normal service_name and environment labels are also present. Outcomes and entity labels are fixed low-cardinality values.

  1. Remove the IOTA route, source adapter, import service, DTOs, runtime schemas, and metrics.
  2. Remove the IOTA form and result workflow from the Admin page.
  3. Remove the Admin page and sidebar group only if no other platform-admin function uses them.
  4. Remove the OpenAPI, API, Platform, contract, operations, and test material that describes this feature.
  5. Remove any no-longer-required DNS or HTTPS egress allowance.
  6. Retire or rotate the IOTA account outside this repository.
  7. Keep the migrated device types and devices.
  8. Keep historical audit events under normal retention.