ignition-core 1.2.0

Core library for ign: config, profiles, gateway client, actions, error taxonomy
Documentation
# webdev/

Gateway-side WebDev route sources for the ign CLI — the CLI's own tag,
alarm, history, and script-execution surface on the gateway (Phase 5).

Every tag operation in the CLI (`ign tags ...`) rides these routes; they are
deployed into a dedicated `ign-cli` project by `ign webdev deploy`, which
zips the bundle **embedded in `ignition-core`** (`src/webdev/mod.rs`) — no
source checkout is needed at deploy time.

## Layout (Designer-native)

```
routes/
  project.json                                # deploy project manifest
  VERSION                                     # route bundle version (handshake)
  com.inductiveautomation.webdev/
    resources/
      cli/
        tags/          (resource.json, config.json, doPost.py)
        tagConfig/     (resource.json, config.json, doPost.py)
        alarms/        (resource.json, config.json, doPost.py)
        tagHistory/    (resource.json, config.json, doPost.py)
        scriptExec/    (resource.json, config.json, doPost.py)
```

On the gateway the URL shape is `/system/webdev/ign-cli/cli/<route>`; each
route is a doPost action-dispatcher (`{"action": ...}`) answering the shared
`version` handshake with `{routeVersion, minCli}` matching
`ROUTE_BUNDLE_VERSION` / `MIN_CLI` in `crates/ignition-core/src/webdev/mod.rs`.

## Security posture

- `tags`, `tagConfig`, `alarms`, `tagHistory` are always-on and open at the
  WebDev layer (`require-auth: false` in `config.json`) — the CLI's own
  gateway credential still reaches the gateway; these routes carry no
  arbitrary-exec capability.
- `scriptExec` is **secret-gated in code** (planner-locked posture): its
  `doPost.py` is a TEMPLATE carrying the `__IGN_CLI_SECRET__` marker.
  `ign webdev deploy` generates the deployed copy by substituting the marker
  with a deploy-time hex secret, presented per call as the
  `x-ignition-cli-secret` (or `Authorization: Bearer`) header and compared
  constant-time in-route. Unconfigured or unsubstituted = every action
  (including `version`) is rejected — fail-closed. The template is
  deliberately NOT part of the embedded `ROUTE_FILES` bundle, so it cannot
  ship unsubstituted by accident.

## Conventions (do not "fix")

- Route folders are SELF-CONTAINED — no cross-resource imports; the shared
  core (unicode re-parse, `jv()` walker, body envelope) is duplicated across
  routes deliberately.
- Body envelope in every response: `{"ok": true, "data": ...}` /
  `{"ok": false, "error": {code, message, traceback?}}` — WebDev ignores the
  `'status'` key, so denials ride HTTP 200 and must be readable from the body.
- Every scripting call is the live-proven form from 05-RESEARCH.md; the
  research's nine prior-art defect corrections are pinned here (string-arg
  `getConfiguration`, basePath `configure`, alarms-as-list, 3-arg
  `acknowledge`, `t_stamp` column, kwargs-only `exportTags`, ...).