# TilliT CLI — agent guide

You are driving `tllt` (`@gotillit/tllt`), a tool for managing TilliT
configuration across three APIs: **Digital Operations (DO)**, **Scheduler**, and
**Wire Flow** (BPMN workflows).

## How to use this tool effectively

1. **Always pass `--json`.** Structured results go to **stdout**; human progress
   goes to stderr. On failure you get `{"ok": false, "error": "..."}` and a
   non-zero exit code. (Or set `TILLIT_JSON=1`.)
2. **Everything is flag-driven** — never rely on interactive prompts. Supply all
   required flags and commands run non-interactively.
3. **Select a connection with `--profile <name>`** (or rely on the default).

Entity snapshots use `.rdata` files; diff/deploy convert them back to API JSON.
Legacy JSON snapshots remain readable. Schema and routing sidecars stay JSON.
See `docs/workflow.md` for supported syntax and grouped entity files.

## The golden path

```bash
tllt configure --tenant <t> --environment <env> --region <eu|us|au> --auth basic \
  --username <u> --password <p> --default      # bootstrap a connection (once)

tllt --json import --profile <t>-<env>   # snapshot live config to files
tllt --json diff --from <A> --to <B>     # changeset to make B look like A
tllt --json deploy --from <A> --to <B> --dry-run   # preview
tllt --json deploy --from <A> --to <B> --yes --reconcile   # apply + normalize
```

Deploy with `--reconcile`: the server normalizes records on write, so the
snapshot you deployed from drifts and future diffs show phantom updates.
`--reconcile` re-imports the touched entities and folds the normalized values
back into your `--from` snapshot. A value the target did **not** keep is left
in the snapshot and reported under `reconciled.notApplied`, and the deploy
returns `ok: false`. Treat that as a failed change, not noise. Records the
server re-keyed or the import filter hides are renamed or dropped and listed
(`rekeyed`, `filteredOut`).

Deploy validates everything before writing anything. On `code:
"validation_failed"` nothing was applied; each entry in `failures` has a `code`
(`unresolved_ref`, `missing_required`, `target_missing`, …) and a `detail`
naming the field, the key and near-miss `candidates`. Fix the snapshot and
re-run. `--allow-partial` applies the valid changes anyway. `diff` and
`--dry-run` list each update's changed `fields` (`before` = target, `after` =
desired), so you don't need to open both files.

## Wire Flow workflows

A workflow is BPMN XML plus a small config object in S3 — not a schema-driven
entity — so `import`/`diff`/`deploy` don't touch it. Use `tllt wireflow`:

```bash
tllt --json wireflow list
tllt --json wireflow pull [id]          # -> {profile}/wireflow/{id}/workflow.bpmn
tllt --json wireflow validate <id>      # compile to ASL, deploy nothing
tllt --json wireflow push <id>          # save + deploy   (--activate, --draft)
tllt --json wireflow test <id>          # invoke once and report the execution
tllt --json wireflow versions <id>      # which version is actually live
tllt --json wireflow activate <id> <n>          # version is POSITIONAL — a --version
                                                # flag is eaten by commander's own --version
```

**`push` deploys but does not activate** — the timer keeps running the previously
activated version until you `activate`. That is the single most common way to
lose an hour here. Read [`docs/wireflow.md`](docs/wireflow.md) **before authoring
or editing any BPMN**; the envelope contract, the expression-rewriting migration,
Map's three sharp edges and the cron format are all non-obvious and all fail
late (at deploy or at runtime, not at save).

## Two more commands you should reach for

- **`tllt api <method> <path> [body]`** — any ad-hoc API call with auth, region
  prefix and tenant header handled. `--paginate` for full lists (raw GETs cap
  at 20 rows and lie about counts). `tllt api POST scheduler/graphql '{...}'`
  reaches the GraphQL endpoint the UI uses — its errors expose the real
  database message when REST errors are opaque.
- **`tllt check`** — read-only integrity audit (duplicate records, ghost order
  shells, route-less operations, case-colliding property values). Run it after
  any bulk load or multi-session work on a tenant.

## Read these for detail

- [`docs/commands.md`](docs/commands.md) — every command, flag, and JSON shape
- [`docs/schemas.md`](docs/schemas.md) — **how to learn each entity's data model** (read before editing records)
- [`docs/authentication.md`](docs/authentication.md) — basic vs api-key/Cognito
- [`docs/workflow.md`](docs/workflow.md) — snapshot layout, diff/deploy semantics
- [`docs/apis.md`](docs/apis.md) — the three APIs and what each owns
- [`docs/scheduler.md`](docs/scheduler.md) — how scheduler config differs (important & different)
- [`docs/wireflow.md`](docs/wireflow.md) — Wire Flow: BPMN authoring traps, deploy vs activate, failure shapes

## Things that will bite you if you forget

- Before creating/editing a record, **read its schema** — either the
  `_schema.json` in the same folder or `tllt schema <Entity>`. It tells you
  required fields, enums, and what each nested `{name}` reference points at.
- A **connection = environment + tenant**; the profile name is `{tenant}-{environment}`.
- Each connection also records a **region** (`eu`, `us`, or `au`). API calls
  are routed through `${baseUrl}/{region}/api[...]` — except Wire Flow, which
  sits *beside* the gateway at `${baseUrl}/{region}/wire-flow/api[...]`; the
  `/api/wire-flow/...` shape 403s with a misleading SigV4 message. Re-run
  `tllt configure --region eu|us|au` if a saved profile errors out with a
  missing-region message.
- `deploy` matches records across environments by **natural key** (the `name`,
  used as the filename), because volatile `id`/`createdAt`/`updatedAt` are
  stripped on import.
- `deploy` **never deletes** unless you pass `--prune`.
- **Scheduler config is organised per `DataTemplate`** and driven through the
  normalized REST API (`/{locationCode}/{dataTemplateId}/{entity}`). Snapshots
  nest under the template name; global entities (`Location`, `DataTemplate`,
  `OptimisationProfile`) sit at the top. Deploy creates a missing template and
  applies records in dependency order (parents first). See `docs/scheduler.md`.
- **For scheduler records, `schema` IS authoritative.** Write shapes match the
  published schema. Nested references are stored by natural key
  (`{ "name": "Bottling" }`) and the CLI resolves them to the target's id on
  deploy. A referenced parent must be in the same deploy or already on the target.
- **Changeover set binds to equipment via the `changeoverSet` ref on create** (a
  required FK — equipment can't be created without it); it persists, no separate
  step. **Availability does NOT** bind via the normalized API — use the gateway
  `availabilities` + `equipment-availabilities` endpoints (≈1yr horizon). See
  `docs/scheduler.md` → "Binding changeover sets & availability to equipment".
- **If another system owns an entity** (e.g. Operations maintained by a
  master-data sync), pass `--protect-entity` on deploy — a prune from a stale
  snapshot once deleted 1,792 sync-owned Operations.
- **Wire Flow deploy is two steps**: `push` creates a version, `activate` points
  the schedule at it. Also: `PUT` config is strictly `{name, description, type}`
  (sending back `stateMachineArn` is rejected), and `validate` only runs the BPMN
  validator — cron and JSONata errors surface at deploy. See `docs/wireflow.md`.
- **Some DO filter operators are silently ignored.** On `core/order-instances`,
  `scheduledStart.greaterThan` works but `.greaterThanOrEqual` / `.gte` return
  unfiltered rows — a bound that looks applied and isn't. Verify a filter changed
  the result set before trusting it.
- **Translations and Articles have side-channels**: the UI reads translations
  from a compiled S3 file and article bodies live behind presigned URLs. Deploy
  handles both (regenerates language files, re-uploads `content`); direct API
  scripts must do the same. Full list of API traps: `docs/apis.md` → "API gotchas".
