# The config surfaces

The CLI manages configuration across three TilliT APIs. All live on the same
per-tenant connection and use the same auth (`basic` or `apikey`); you select
entities with `--api do` / `--api scheduler`, and routing is handled for you.
Wire Flow is not entity-based, so it has its own command family
(`tllt wireflow`) rather than an `--api` value.

## DO — Digital Operations

- The main configuration surface (assets, attributes, activities, …).
- Import is **schema-driven**: the CLI learns each entity's data model from the
  live schema and expands related records so the snapshot is self-describing.

Entities snapshotted today (`./{profile}/do/...`), grouped by domain:

- **Asset model**: `Asset`, `AssetClass`, `AssetProfile`, `AssetStatusColor`,
  `AssetAttribute`, `AssetTolerance`, `AssetMeterTemplate`.
- **Attributes & process variables**: `Attribute`, `AttributeGroup`,
  `AttributeGroupItem`, `AttributeGroupAssignment`, `ProcessVariable`,
  `UnitOfMeasure`, `EventType`.
- **Sites, calendars, shifts**: `Site`, `SiteAttribute`, `ShiftTemplate`,
  `ShiftTemplateItem`, `Calendar`, `CalendarItem`.
- **Materials**: `MaterialGroup`, `MaterialProperty`, `Material`,
  `MaterialAttribute`, `MaterialComponent`, `MaterialConversion`,
  `MaterialTolerance`.
- **Inventory movements**: `MovementType`, `MovementTypeField`.
- **Alarms**: `AlarmType`, `AlarmTypeAssignment`.
- **Edge & data sources**: `DatasourceTemplate`, `DatasourceTemplateTag`,
  `DatasourceTemplateTrigger`, `DatasourceTemplateAssignment`, `Edge`,
  `EdgeDataSource`, `EdgeDataTag`, `EdgeTrigger`.
- **Events**: `EventRelay`, `EventSchedule`.
- **Control parameters (recipes)**: `ControlParameter`, `ControlParameterRecipe`,
  `ControlParameterRule`, `ControlParameterRuleAttribute`.
- **Run rates**: `RunRateTemplate`.
- **Order templates**: `OrderStatus`, `OrderNumberAttribute`, `OrderTemplate`,
  `OrderTemplateComponent`, `OrderTemplateDependency`.
- **Activities**: `ActivityClass`, `ActivityItemOptionGroup`,
  `ActivityItemOptionItem`, `ActivityTemplate`, `ActivityTemplateItem`,
  `ActivityStarter`, `ActivityAssignment`, `ActivityProcessStarter`,
  `ActivityProcessTrigger`, `ActivityAssignmentAttribute`.
- **Translations & articles**: `Translation`, `Article` (metadata only — see
  the gotchas below for how content and the compiled language files work).

To add an entity, extend `DO_ENTITIES` in `import.js` (entity name + optional
filter / filename / folder nesting). Parents must appear before their children
so deploys create them in the right order.

## Scheduler

- Production-scheduling configuration, driven through the **normalized REST API**
  (`/api/scheduler/{locationCode}/{dataTemplateId}/{entity}`). **Organised per
  `DataTemplate`** — snapshots nest under each template, and records reference one
  another by natural key. See [`scheduler.md`](scheduler.md) before touching
  scheduler config.

Entities snapshotted today (`./{profile}/scheduler/...`):

- **Global**: `Location`, `DataTemplate`, `OptimisationProfile`.
- **Equipment**: `EquipmentClass`, `Equipment`, `EquipmentClassMembership`.
- **Personnel**: `PersonnelClass`, `Personnel`, `PersonnelClassMembership`.
- **Availability & downtime**: `AvailabilityTemplate`, `AvailabilityTemplateItem`,
  `DowntimePeriod`, `EquipmentDowntimePeriod`, `PersonDowntimePeriod`.
- **Changeovers**: `Property`, `PropertyValue`, `ChangeoverSet`,
  `ChangeoverSetItem`.
- **Materials**: `MaterialGroup`, `MaterialDefinition`, `MaterialProperty`.
- **Routing**: `Operation`, `Route`, `Segment`, `SegmentMaterial`,
  `SegmentEquipment`, `SegmentDependency`.

To add an entity, extend `SCHEDULER_ENTITIES` in `lib/scheduler-entities.js`
(descriptor: endpoint, scope, natural key, and any references).

## Wire Flow

BPMN workflows, not config entities: a workflow is BPMN 2.0 XML plus a small
config object stored in S3, so there is nothing schema-driven to import and
`import`/`diff`/`deploy` do not manage it. Driven through `tllt wireflow` —
see [`wireflow.md`](wireflow.md).

The one routing surprise: this API sits **beside** the `/api` gateway rather than
under it.

```
${baseUrl}/{region}/wire-flow/api/workflows       correct
${baseUrl}/{region}/api/wire-flow/api/workflows   403, SigV4 "requires 'Credential'"
```

That 403 means *wrong path*, not wrong credentials — the same profile auth works
once the path is right.

## Entity schemas

Both surfaces publish a per-entity schema that the CLI fetches on import and
exposes via `tllt schema`. See [`schemas.md`](schemas.md) for how to read
it — it's authoritative for both APIs.

## API gotchas (hard-won — read before scripting against the APIs)

Field-level traps:

- **Raw GET lists paginate at 20 rows** and often ignore `size`; the flattened
  gateway lists cap at 200. Never verify counts with a single GET — use
  `tllt api ... --paginate`, `tllt import`, or the normalized
  `/{loc}/{tpl}/{entity}?size=10000` endpoints.
- **The UoM endpoint is `core/unit-of-measures`** (`core/uoms` 404s).
- **`OrderTemplate.variableQuantity` is a NUMBER (multiplier), not a boolean** —
  `true` fails validation; `1` passes the full quantity to the child asset.
- **Some PUTs are full replaces** that null omitted fields
  (`core/event-instances`, `core/shift-instances`, `core/order-instances`),
  while others merge (`activity/activity-templates`). When updating a
  replace-style endpoint, GET first and resend the complete payload.
- **References resolve by numeric `id`**, not names or uuid keys: e.g.
  `POST core/articles` silently ignores a uuid-string `parent`;
  `POST core/order-components` stores `component: null` when given
  `{externalId}` — both need `{"id": N}`.

Behavioural traps:


- **Translations are served from a compiled per-language S3 file**
  (`core/translations/file/{lang}`), not the table. After writing translation
  rows, `POST core/translations/update-language-file {"language": "..."}` per
  language, or the UI never sees the change (deploy does this automatically).
  The Translations screen only lists keys present in the *current* UI language,
  so create paired rows (e.g. `en` + `da`) for every key.
- **Article content lives behind presigned URLs**: every create/update response
  returns a fresh `preSignedUrl`; upload `{"content": "<quill html>"}` to it —
  including after metadata-only updates, or the body is lost.
- **Some filter operators are silently ignored, not rejected.** On
  `core/order-instances`, `scheduledStart.greaterThan` filters, but
  `.greaterThanOrEqual` and `.gte` return the unfiltered set — a bound that looks
  applied and isn't. Always confirm a filter actually changed the result set.
- **`orders-stats` beats `order-instances` for anything asset-shaped**: it serves
  every status (not just RUNNING), and carries `assetId`/`assetName` plus
  quantities, which `order-instances` does not expose at all. But its `assetId`
  filter is **hierarchical** — it returns the asset *and its descendants*, so
  asking for a line yields its equipment's orders. Use
  `order-instances?asset.id.equals=` when you need an exact match.
- **Planned orders belong in the Scheduler** (`scheduler/orders/upsert`), not
  DO — `core/order-instances/upsert` is for execution orders and silently skips
  unresolvable assets.
- **MQTT order-progress counts are increments**, not cumulative counters — the
  platform adds each message's counts to the order.
- **Don't run a snapshot deploy and a direct-API bulk batch concurrently** —
  the tenant API starts timing out; sequence them.
