# Changelog ## 0.1.6 — 2026-09-07 - **A valid lease token is reused across applies.** Every apply requested a new lease, so a failing deploy retried by the 60 s timer burned tenancy's 5-leases-per-hour budget and then failed on `LEASE_RATE_LIMITED` forever (env-qa-02 pilot). Now: reuse a lease-derived token while `lookup-self` says it is valid; swap the bootstrap token for a lease once; and if tenancy rate-limits the lease while a working token exists, apply with it and defer the swap instead of failing the deploy. ## 0.1.5 — 2026-09-07 - **`tenancy.port` defaults to 443 everywhere** — the ansible role default and the `TenancyCfg` default now match `install.sh`/`config.example.yaml` (0.1.5 only changed those two); PROTOCOL.md states the intercept port separately from the in-pod 8081, that the broker does not add `#monky-deploy-agent` yet, that the jwt mount keeps no `jti` state, and that `release:gitea` has not run (doc-drift DD-0523/0525/0527/0528). - **`${VAR}` inside comment lines is not a reference.** The renderer's `.env.template` header literally says "substitutes every ${VAR}", which the refusal check counted as an unresolved variable (`ENV_INCOMPLETE: unresolved: VAR`) — the first bundle on env-qa-02 was refused for it. - **config: `tenancy.port` is the service's intercept port (443), not the in-pod 8081.** With 8081 the SDK found no intercept and the dial failed (`service not available`, then a bare `TypeError` from the SDK's fallback). `install.sh` now writes 443; `config.example.yaml` updated. - **transport sdk: clear error instead of a TypeError** when an address has no intercept or the identity has no dial policy for the service (`TransportError` names the host:port and what to check). ## 0.1.4 — 2026-09-07 - **install.sh: `ziti-edge-tunnel.service` failed to start after enrolment** (`203/EXEC`: the package's `ExecStartPre` script is not executable by user `ziti` on ziti-edge-tunnel 1.18.x) and, because the unit was enabled with `--now` under `set -e`, the script aborted BEFORE writing config/ACLs/grant/timer — a second kit was needed. The run-host drop-in now clears `ExecStartPre=`; a tunneler start failure is a WARNING until everything else is in place, then a hard error at the end. ## 0.1.3 — 2026-09-06 - **install.sh: `--version` was clobbered by `/etc/os-release`** (it defines `VERSION`), so the kit tried to download `monky-deployd_26.04 LTS (Resolute Raccoon)_amd64.deb` (env-qa-02 pilot, first real kit run). Script variables are now `DEPLOYD_VERSION`; os-release is read in a subshell. - **install.sh installs Docker Engine when absent** (Docker's apt suite for the codename → fallback `docker.io`), with `--docker-data-root ` to place the data-root before first start. One script for the end user, per the operator's rule. ## v0.1.2 — 2026-09-05 - **The GitLab project is private** (its parent groups are private, so it cannot be made public; found when the v0.1.1 one-liner returned 401 anonymously). `install.sh` gains `--token ` / `MONKY_DEPLOYD_TOKEN`: every download from the generic package registry — the script itself included — sends `DEPLOY-TOKEN: `, a read-only GitLab **deploy token** (scope `read_package_registry` only, revocable), seeded in OpenBao at `monky/monky-tenancy/deployd-download` (key `token`) and handed to the box by the monky-tenancy install kit. The token never reaches the command line, the log or an xtrace (curl `-K` config file, 0600, deleted after the download; `set +x` forced). The one-liner now fetches `install.sh` from the registry (`…/monky-deployd//install.sh`) instead of `-/raw/main`, and takes the bootstrap grant via `--bootstrap-file` — with `curl … | bash -s --` stdin IS the script, so it is no longer read for the grant in that mode. `--source gitea` (no token) stays the off-estate alternative. - Ansible role: `monky_deployd_download_token` (vaulted) → `DEPLOY-TOKEN` header on both fetches, `no_log: true`. - README / OPERATIONS: why (private project; split-horizon Gitea, cbs/iac#102), token scope, revocation. No agent behaviour change. ## v0.1.1 — 2026-09-05 - `install.sh` / ansible role / README: the **primary download is the public GitLab project's generic package registry on scm.tikali.ai** (`/api/v4/projects/69/packages/generic/monky-deployd//…`); the one-liner fetches the script from `scm.tikali.ai/…/-/raw/main/packaging/install.sh`. Inside the estate `gitea.cbs.tikali.net` is split-horizon to jump1's RED EIP (no HTTP ingress), so backend boxes could not fetch the artefacts (cbs/iac#102). The Gitea release stays the off-estate alternative (`--source gitea` / `MONKY_DEPLOYD_SOURCE=gitea`). No agent behaviour change. ## v0.1.0 — 2026-09-05 First release (MONKY-ADR-0028 §D, Reconciliation v2, Gate 1 v2). - Stdlib-only Python 3.12 agent: `run --once|loop`, `status`, `bootstrap`, `--prune`. - Protocol: `POST /v1/agent/checkin` (`action apply|none|down`), `GET /v1/agent/bundle/{env}/{sha}` (sha256 verified), `POST /v1/agent/lease` → **deploy grant** (`login_jwt`), `POST /v1/agent/report` (`result applied|failed|down`, redacted `log_tail`). An AppRole-shaped lease is refused (`LEASE_SHAPE`). - OpenBao: `POST /v1/auth/jwt-tenancy/login {"role":"see-env","jwt":…}`; KV-v2 reads pinned to the manifest's versions, paths pinned to `monky/data//see/`; renew-self / re-lease before max TTL. - Refusals: `ENV_INCOMPLETE` (names only), `PRIVILEGED_REFUSED`, `ROLLBACK_REFUSED`, `DISK_INSUFFICIENT` (`need × 1.5 + 2 GiB` vs docker data-root), `BUNDLE_SHA_MISMATCH`, `BUNDLE_ENV_MISMATCH`. - Transports `sdk` (openziti SDK), `proxy` (`ziti tunnel proxy` 18443/18200), `system`. - Exit codes 0 / 75 / 78 (`AGENT_ENV_MISMATCH`, no retry storm) / 1; laptop mode (offline exits 0). - Packaging: hardened `monky-deployd.service` oneshot + 60 s timer, `monky-deployd-proxy.service`, `nfpm` `.deb` with `/opt/monky-deployd/venv`, `packaging/install.sh` (Ubuntu 26.04), ansible role skeleton. - Known divergence: monky-tenancy `main` (MR !15) still ships the AppRole lease/kit; the JWT-grant follow-up is the tenancy side of this release (docs/PROTOCOL.md "Divergences").