# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [1.0.22] - 2026-09-21

### Security

- **Bumped the indirect `google.golang.org/grpc` to v1.83.2** for GO-2026-6348,
  published while this release was being prepared. grpc arrives via
  `sigstore/sigstore-go`, which is already at its latest (v1.3.0) and still
  pins the affected line, so the indirect dependency is raised directly.
  `govulncheck` under the CI platform (`GOOS=linux`) reports no reachable
  vulnerabilities afterwards.

### Fixed

- **`workflow submit` / `sqlcheck` / `auto-review` returned 404 on Archery
  v1.9.0 and later.** Archery deleted the root-level AJAX routes `/autoreview/`
  and `/simplecheck/` in v1.9.0 — along with `sql_workflow.submit` and
  `sql_workflow.check`, the views behind them — and the session transport was
  still calling both. Against any v1.9.0+ server every submit and every
  pre-check failed with a 404 that no combination of instance, resource group
  or SQL could get past.

  All three commands now use Archery's REST endpoints, `POST /api/v1/workflow/`
  and `POST /api/v1/workflow/sqlcheck/`, in **both** transport modes. Those
  routes also exist in v1.8.5, so one path covers every supported version;
  `--mode jwt` is not required and nothing in the flag surface moves. The
  session transport gained a JSON body (`SessionPostJSON`) to reach them:
  Archery lists `SessionAuthentication` beside `JWTAuthentication` in its DRF
  `DEFAULT_AUTHENTICATION_CLASSES`, and both endpoints override the global
  API-whitelist permission with `IsAuthenticated`, so the session cookie the
  CLI already holds is accepted — no JWT, no whitelist entry.

  Three wire details differ from the retired views and are now pinned by tests,
  because each one fails only against a live server: the submit payload nests
  every workflow field under `workflow` with `sql_content` beside it, its
  instance key is the FK name `instance` (not `instance_id`), and the response's
  top-level `id` is the *content* row — the workflow id is `workflow_id`. The
  pre-check names its SQL field `full_sql`, not `sql_content`.

- **A 404 no longer hides which route was missing.** The fallback message for a
  404 with no body was a bare `resource not found`, which reads like a bad
  argument and sends the caller auditing parameters for what is a route-level
  failure. It now names the method and path and says what a 404 means here.
  This is the diagnostic whose absence made the bug above cost an afternoon.

- **A malformed pre-check response is no longer reported as a clean check.**
  The REST pre-check decoder tried several envelopes in turn and ignored the
  failure of each, so a body it could not read returned `ok:true` with an empty
  result list — a pre-production SQL gate answering "no findings" when it had
  in fact parsed nothing. There is now one envelope (the serializer's shape is
  identical from v1.8.5 to v1.14), and a body that does not match is an error.

  Both decode failures were also returned as plain errors, which map to
  `E_NETWORK`, so a contract mismatch was retried three times before failing.
  They surface as `E_UNKNOWN` now, matching `workflow detail`. A 2xx submit
  response carrying no workflow id is likewise an error rather than a success
  with id 0.

- **`--read-only` again refuses `workflow submit` before any network call.**
  Resolving the instance and resource group now queries the server, which would
  have happened before the read-only gate at the confirm chokepoint. The
  refusal moved ahead of the resolution, restoring the stronger promise: a
  write command under `--read-only` issues no request at all.

- **Write commands were unusable in `--format text`.** The dry-run printed the
  action and nothing else — no preview, and above all no confirm token — while
  the same command instructed the caller to "retry with `--confirm
  <confirm_token>`". There was no token to retry with unless they knew to
  switch to `--format json`. Text mode now mints the same token, prints the
  preview fields being approved, and shows the flag to re-run with. It does
  not reconstruct a paste-ready command line: arguments routinely carry SQL
  with spaces and quotes, and cmd.exe and POSIX shells disagree on quoting, so
  a wrong line would be worse than none.

- **Workflow links pointed at a route that does not exist.** Every `webUrl`,
  every `URL` line in `workflow detail`, and the link returned by a successful
  `workflow submit` was built as `<host>/sqlworkflow/<id>/`. Archery routes
  `sqlworkflow/` to the workflow *list* page, which takes no id segment, so
  that URL matched nothing and 404'd — for every workflow the CLI has ever
  printed, on v1.8.5 through v1.9.x alike. The detail page is `/detail/<id>/`,
  which is also where Archery's own submit view redirects and where the CLI
  already read the new workflow's id from.

- **Resource groups now resolve for non-superuser accounts.** Moving submit to
  the REST endpoint meant `--group-name` had to be resolved to a numeric
  group_id, and the only listing the CLI knew, `/group/group/`, is
  `@superuser_required` — it returns an empty list to an ordinary account
  rather than an error. Every non-superuser therefore got `resource group "x"
  not found` and could not submit at all, with the message pointing at
  `user resource-groups`, which was empty for the same reason. The CLI now
  falls back to the groups Archery renders into its own submit page — the one
  place a non-admin can see the id — and `user resource-groups` uses the same
  fallback so its advice is true. A group that really is missing now lists the
  ones that are available instead.

- **A token mismatch now says what the fingerprint covers.** `confirmation
  token does not match this operation` gave no way to guess *which* dimension
  differed. It now names the command and the active region context, and says
  the arguments are covered too. The common cause is the region context
  differing between the two steps — for instance `ARCHERY_CLI_REGION` set on
  only one of them, which is exactly how this was found.

- **`auth login --dry-run` no longer asks for a password it cannot use.** The
  password was collected before the gate, so a dry-run took the secret and
  discarded it — which reads like the password was rejected — and in json mode
  `--password` was *required* to obtain a token for a step that sends nothing,
  pushing the secret into argv where other processes and the shell history can
  read it. A dry-run now needs no password in either mode; the confirm step
  prompts for it (text) or takes `ARCHERY_CLI_PASSWORD` / `--password` (json).

### Changed

- **The `auth login` confirm token no longer binds the password.** It binds
  what the operator actually previewed — region, url, username, mode. The
  password is an input to the login, never shown in the preview and so never
  something they could approve or reject; binding it only forced the dry-run
  to collect a secret in order to reproduce the same fingerprint at confirm
  time. A wrong password now fails authentication rather than masquerading as
  a token mismatch.

- **`--instance-name` / `--group-name` are resolved to numeric IDs** (the REST
  endpoints key on the ID), the reverse of the previous session-mode direction.
  Both spellings still work in both modes. `workflow submit` resolves whichever
  side is missing, so its dry-run preview always names the instance and group,
  and an identifier that matches nothing fails at `--dry-run` with
  `E_NOT_FOUND` rather than after an operator has approved a token.

### Removed

- **`workflow submit --cc`.** Only the deleted `/autoreview/` view ever carried
  `cc_users`; it is not a field on Archery's workflow model or serializer. The
  flag stays registered and is rejected with `E_VALIDATION` naming the reason,
  so an agent working from an older Skill gets an explanation instead of an
  "unknown flag" — but the recipients cannot be delivered, and silently
  dropping a notification list would be worse than failing. Configure
  carbon-copy recipients in Archery's notification settings instead.

## [1.0.21] - 2026-09-07

### Added

- **Unattended 2FA via a stored TOTP secret.** `auth login --totp-secret <base32>`
  (or `ARCHERY_CLI_2FA_SECRET`) stores the shared secret behind the enrolment QR
  code, and the CLI then derives its own 6-digit code to complete Archery's
  `/api/v1/user/2fa/verify/` handshake. An agent or CI job on a 2FA account no
  longer dead-ends on `E_2FA_REQUIRED` waiting for a human to read a phone.
  `--otp` still wins when given, so an operator can override a rotated secret.
  `context` reports `regions[].hasTotpSecret` so a caller can tell "2FA is
  handled" from "2FA will block", and `auth logout` deletes the secret with the
  rest of the region's credentials.

  The secret goes only to the OS keyring, alongside the JWT and session entries
  and under the same `region|kind|user` key (SEC-SPEC §4); the config file keeps
  zero secrets. A malformed secret is refused by `--dry-run`, before any network
  call, so the preview cannot promise a login that `--confirm` would fail.

  RFC 6238 is implemented on the standard library rather than added as a
  dependency: the algorithm is HMAC-SHA1 plus dynamic truncation, it is pinned
  by the RFC's own test vectors, and the usual Go TOTP module also carries a
  QR-code encoder this tool has no use for.

  **Trade-off, stated plainly:** a stored seed puts both factors on one host, so
  that host no longer has two-factor auth — it has two secrets in one place.
  Prefer `ARCHERY_CLI_2FA_SECRET` over the flag (argv is visible in process
  listings), and do not do this for a high-privilege account.
## [1.0.20] - 2026-08-26

### Security

- Raise the Go toolchain to 1.26.6 to clear four reachable Go standard-library
  vulnerabilities reported by `govulncheck`: GO-2026-6218 (`net/url`),
  GO-2026-6090 (`crypto/tls`), GO-2026-5972 (`encoding/asn1`), and
  GO-2026-5026 (`net/http`).
- CI now runs `govulncheck ./...` in the Lint job, fail-closed. SEC-SPEC §5 was
  tightened to require a dependency audit for every ecosystem a tool ships in;
  this one ships as a Go binary and an npm wrapper, and only the wrapper was
  ever audited, so the Go module's own dependencies went unscanned.

### Changed

- Sync the vendored spec to `ai-native-cli-spec@v1.6.0`. Besides the audit rule
  above, CLI-SPEC §14 now states the `update` final-state contract explicitly:
  the idempotent no-op check runs before any package-manager command, and both
  successful and no-op results report `current_version == target_version` with
  `update_available: false`.
- Sync the vendored spec to `ai-native-cli-spec@v1.6.1`. `gen-contract.js` no
  longer falls back to unformatted output when its source formatter is missing:
  the generated bytes are what `check-spec.js` byte-compares against, so a
  silent fallback made a fail-closed drift guard answer differently depending on
  what was installed. A missing formatter is now an error with exit 2.

### Fixed

- The CI job that runs `check-spec.js` now sets up Go. It had Node only and
  worked purely because the GitHub runner image happens to preinstall Go — one
  image change away from every Go tool in the fleet reporting phantom contract
  drift at once. The codegen verifier now gets the codegen's own toolchain.
- **The release path now gates on the same checks as the merge path.**
  `release.yml` re-ran formatting, vet and tests at tag time but never ran
  `govulncheck` or `check-spec.js`, so a CVE published after the last green run
  on main shipped signed and published — which is exactly what would have
  happened this month. It now runs both, Linux-only, before the build. A
  release blocked by a fresh upstream advisory is the intended outcome.
- The two steps that shell out to `apt-get` (`Project-specific clean check` and
  `Ensure race detector toolchain`) are bounded with `timeout-minutes: 5`. They
  normally take seconds; one stalled for close to an hour and had to be
  cancelled by hand, which without a bound would have run to the six-hour job
  default.

## [1.0.19] - 2026-07-08

### Fixed

- `update` success now reports final post-install state (`current_version == target_version`, `update_available: false`) and clears the cached update notice after the binary or package manager commits.
- Post-swap Skill-sync partial-success details now also report `target_version` and `update_available: false`, so agents can tell the binary is already at the target version even though the Skill still needs syncing.
- Windows Go test binaries (`*.test.exe`) no longer read or write the real user update-notice cache by default.

## [1.0.18] - 2026-07-02

### Fixed

- **Env credentials now follow `--region` instead of the config default.** `ARCHERY_CLI_USERNAME`/`ARCHERY_CLI_PASSWORD`/`ARCHERY_CLI_URL` overrides were applied to the *active/default* region, so `archery-cli <cmd> --region X` with env creds silently left region X without those credentials (and you had to set `ARCHERY_CLI_REGION=X` as a workaround). The `--region` flag now sets the effective region (precedence: `--region` > `ARCHERY_CLI_REGION` > `default_region`) before env overrides resolve, so credentials land on the region the command actually targets (A-5, CLI-SPEC §11).
- **`npm ci` lockfile drift fixed.** The per-platform `optionalDependencies` subentries in `package-lock.json` (`node_modules/@fateforge/archery-cli-*`) were missing their `version`, so once those platform packages were published, `npm ci` failed its consistency check (`lock file's <pkg>@ does not satisfy <pkg>@<version>`). The version bump (`scripts/version-files.js`) now syncs the lockfile platform subentries too, and `check-version.js` guards them, so the drift can't silently recur.

## [1.0.17] - 2026-06-29

### Fixed

- **Session/2FA login on jwt-mode regions now actually completes.** Archery's `/api/v1/user/2fa/verify/` requires an `auth_type` form field (default `totp`, overridable via `ARCHERY_CLI_2FA_TYPE`) that the client previously omitted, so every 2FA login silently failed (and was then masked as success — see below). Session-only commands (`query`/`dict`/`diagnostic`/...) on a 2FA account can now authenticate.
- **2FA / session login success is now positively verified (fail-closed).** `complete2FA` and `ensureSession` previously read a *failed* verify (HTTP 400, or a DRF `{"detail":...}`/field-error body with no `status` key) as success: the HTTP status was ignored, the Archery `status` field defaulted to zero, and a self-injected temporary session cookie made the cookie check always true. Success now requires `status < 400`, a parsed Archery `status == 0`, and — for 2FA — the `sessionid` to actually rotate away from the replayed temp `session_key`. A non-JSON/HTML body fails closed instead of being read as an empty success (CLI-SPEC §1.6/§12.7).
- **Login-page redirects now surface `E_AUTH`, not a JSON-parse `E_SERVER`.** An unauthenticated session request is bounced (302) to `/login/`; the HTTP client used to follow it into the HTML page and report `failed to parse query response: invalid character '<'`. Session/REST requests no longer follow login redirects and map a 3xx to `E_AUTH` (exit 4); the login-page match tolerates a deployment base path (`/archery/login/`) (CLI-SPEC §6).
- **2FA-required failures on session commands now report `E_2FA_REQUIRED` (exit 9), not `E_AUTH` (exit 4).** The per-item/session error classifier ignored an `APIError`'s explicit `Code`, so a missing-OTP failure on `query`/`dict`/etc. collapsed to the status-derived `E_AUTH` — telling an agent to fix credentials instead of supplying `--otp`. It now honors the explicit code, matching `handleAPIError` (CLI-SPEC §6, §16.3).

### Changed

- **Django sessions are persisted in the OS keyring and reused across commands on jwt-mode regions.** Session-only commands on a jwt region previously re-ran the full form login — including a fresh 2FA OTP — on every invocation. A lazily-established session is now cached and reused until it expires; a cached session the server has since rejected is transparently re-authenticated once before surfacing `E_AUTH` (CLI-SPEC §16.1).
- **`auth login` falls back to `ARCHERY_CLI_USERNAME` / `ARCHERY_CLI_PASSWORD`** when `--username` / `--password` are omitted, so a non-interactive `auth login --region <name>` works without echoing secrets in argv (SEC-SPEC §4).

## [1.0.16] - 2026-06-29

### Fixed

- `update` Sigstore signature verification now bounds the TUF trust-root refresh with a timeout and binds it to the command context. A hung or slow Sigstore TUF registry can no longer stall the `verify_signature` stage indefinitely, and a SIGINT during the refresh is honored instead of being swallowed until the fetch returns (CLI-SPEC §14).
- `update` notice cache reads are now version-aware: a cached `update_available` notice is suppressed once the running binary is already at or past the cached latest version. Business commands no longer keep advertising an update to a version that is already installed for up to the 24h cache TTL (for example, right after a successful update).
- Install-method detection no longer classifies a standalone binary as pip-managed just because its path contains the substring `pip` or `conda`. Only genuine pip/venv layouts (a `site-packages`, `venv`/`.venv`, or `lib`+`python` path segment) are treated as pip, so `update` cannot misroute into a bogus `pip install` for a binary that merely lives under a directory whose name happens to contain those letters.

## [1.0.15] - 2026-06-25

### Added

- **Fleet contract single-source bootstrap** (`contract/contract.json`, `internal/contract/contract_gen.go`). The canonical E_* → exit → retryable table and the JSON schema version are now generated from `ai-native-cli-spec@v1.4` (`.agent/SPEC_VERSION=v1.4`) and checked by a new `scripts/check-spec.js` CI guard.
- **Contract conformance test** (`internal/output/contract_conformance_test.go`). Every emitted error code is asserted against the canonical contract — exact exit + retryable + SchemaVersion — so drift is CI-red.
- **`update` drives the package manager** (npm/pip) rather than printing `package_manager_required`. A bare `update` on an npm/pip-managed install now executes the install command on the user's behalf (via the testable `updateRunPackageManager` seam), then syncs the Skill. `--dry-run` stays read-only (preview only). A PM failure returns `E_IO`, `binary_replaced:false`, with the command in details.
- **`contract/contract-ext.json`** registers `E_2FA_REQUIRED` (exit 9, retryable false) as an archery-cli extension code.

### Changed

- **`.agent` synced from `ai-native-cli-spec@v1.4`** (AGENT.md, CLI-SPEC.md, SEC-SPEC.md, SKILL-SPEC.md and zh variants).
- **`E_RATE_LIMIT` renamed to `E_RATE_LIMITED`** to match the canonical fleet code name. The string value emitted in JSON changes accordingly; any agent parsing the old string must update.
- **`internal/output.SchemaVersion`** is now `contract.SchemaVersion` (no more hardcoded `"1.0"` that could drift).
- **`internal/output.RetryableErrorCode`** now delegates to `contract.Retryable` (single source of truth).
- **`internal/output.ExitCodeForErrorCode`** now delegates to `contract.ExitFor` (single source of truth).
- **`cmd.exitForErrorCode`** replaced with a one-liner `return output.ExitCodeForErrorCode(code)` — eliminates missing-case drift (previously missing `E_CONFIRMATION_REQUIRED` and `E_UNKNOWN`).
- **CI**: added `Verify spec/contract sync` step (`node scripts/check-spec.js`) in the `npm-audit` job, right after `Verify version sync`.

## [1.0.14] - 2026-06-25

### Removed

- **Dropped the dead `update --channel` (`stable`/`canary`) flag.** It was read, validated, stored on the plan, and emitted as `update_report.channel`, but never affected release resolution — both values resolved the same `/releases/latest` (or `/tags/v*`) URL. The flag, its `stable`/`canary` constants, the `channel` plan field, and the `channel` output key are gone, aligning `update` with the rest of the fleet (no channel concept).

### Changed

- **Windows self-update now replaces the binary in place atomically**, matching Unix. The previous flow staged a `.new` file and spawned a `.cmd` script to `move` it over the target after the process exited (status `scheduled`, `pending_path`). It now uses the same rename trick on every platform: write `.<base>.new` → rename the in-use binary to `.<base>.old` (Windows permits renaming a running executable) → rename `.new` into place → restore from `.old` on failure → remove `.old` on success (ignored if Windows still has it locked). `update` therefore returns `status: "installed"` / `binary_replaced: true` with no restart required on Windows; the `scheduled` status and `pending_path` field are gone.

### Fixed

- **`update` failures are now classified onto the error taxonomy instead of collapsing into `E_NETWORK`/hardcoded exits.** Concretely:
  - The error code → exit code mapping is now derived from a single function (`exitForErrorCode`); `update`'s failure helpers no longer hand-pass an exit code alongside the error code, so the two can never drift. As a result a SIGINT during the post-swap `skill_sync` stage now exits `130` (`E_INTERRUPTED`) instead of the hardcoded `7`.
  - `discover`/`download` HTTP failures are split by upstream status — `403 → E_FORBIDDEN`, `404 → E_NOT_FOUND` (non-retryable), `408 → E_TIMEOUT`, `429 → E_RATE_LIMITED`, `5xx → E_SERVER` — instead of every transport failure looking like a transient `E_NETWORK`.
  - In `verify_signature`, only an actual signature/checksum verdict is `E_INTEGRITY` (non-retryable). Fetching the signature bundle is a network step whose failure is now a retryable network class, and a SIGINT mid-verify surfaces `E_INTERRUPTED` (exit 130) — never a forged-release verdict an agent must stop and report. A `ctx` cancellation check was added before `verify_checksum` as well.
- **`internal/output.ErrorCodeFromStatus` now maps `408` to `E_TIMEOUT` (retryable).** It previously fell through the default `4xx` branch to `E_USAGE` (non-retryable, exit 2) — the opposite of the contract.
- **`update --dry-run` is reachable for package-manager (npm/pip) installs.** The read-only preview is now decided before the `package_manager_required` short-circuit, so a managed install still gets a preview (with the package-manager command) instead of being routed straight to `package_manager_required`.

## [1.0.13] - 2026-06-22

### Added

- **The update-available notice now also rides on every command's `meta.notices`.** When the local update-check cache holds an available-update notice, it is attached read-only to `meta.notices` on every command's envelope (success and error), in addition to the existing fresh `data.notices` on `context`/`doctor`/`update`. This path does zero network I/O — it is a single local cache read, TTL-bounded — so business commands surface a pending update without ever phoning home. The field is `omitempty`: absent when the cache is empty or expired.
- **Update notices are now severity-graded** instead of a hardcoded `info`. Severity is computed at check time from the embedded CHANGELOG delta between the running version and the latest: `warning` when the delta contains a `security` entry OR the latest crosses a major version (likely security-relevant or breaking), otherwise `info`. The graded severity is stored in the cache, so the cached `meta.notices` carries the right level. (`critical` stays reserved for known-yanked/known-vulnerable versions.)

## [1.0.12] - 2026-06-21

### Changed

- **`update` is now a single command with no confirm token.** A bare `archery-cli update` performs the whole self-update in one call — resolve the latest (or `--target-version`) release → verify the Sigstore signature → verify the checksum → replace the binary → sync the Skill directory. The old dry-run→confirm-token write gate has been removed from `update` only (other write commands are unchanged). `--check` and `--dry-run` stay as optional read-only flags, and `update --dry-run` now issues no `confirm_token`/`expires_at`. `update` is idempotent: already-latest returns a no-op success. The in-process Sigstore verification (signature then checksum, fail-closed, non-retryable) is unchanged and remains the safety gate.

### Added

- **Staged failure and interruption envelope for `update`.** Every update failure carries `stage` (`discover|download|verify_signature|verify_checksum|replace|skill_sync`), `current_version`, `binary_replaced`, and `skill_sync_status`. A skill-sync failure after a successful binary swap is now **partial success** (`ok:false`, `binary_replaced:true`, with `skill_sync_command`) instead of a hard error that hid the completed swap. SIGINT/SIGTERM is trapped: the temp dir is cleaned and a terminal JSON envelope is still emitted (`E_INTERRUPTED`, exit 130) instead of dying as a bare killed process.
- **New error codes `E_IO` (exit 1) and `E_INTERRUPTED` (exit 130).** Added to the output package and the status→code→exit mapping.

### Fixed

- **`update` replace-stage failures are no longer misclassified as `E_NETWORK`.** Local filesystem failures during extract/replace now surface as `E_IO` (exit 1), and permission failures as `E_FORBIDDEN` (exit 4) — non-retryable, with the concrete fix — instead of looking like a transient network blip.

## [1.0.11] - 2026-06-20

### Fixed

- **`instance resource` now works in `--mode jwt` instead of returning `data: null`.** The REST endpoint `POST /api/v1/instance/resource/` returns its rows as `{"count": N, "result": [...]}` (singular `result`), but `extractResourceItems` only recognised the DRF-plural `results` (and an internal `data.rows`), so the list fell through to `nil` and the command emitted an `ok: true` envelope with `data: null` — listing no databases/schemas/tables/columns at all on JWT-only deployments. The parser now handles the singular `result` key; flat scalar rows normalize to `{"name": …}` exactly as the session path does. Reproduced and verified live against `hhyo/archery:v1.8.5` in `--mode jwt` (database/table/column all return rows). Fixes #13.

### Notes

- **`query run` and `instance describe` are session-only by Archery's design.** Ad-hoc query execution (`/query/`) and describe-table (`/instance/describetable/`) have no REST/JWT equivalent in Archery's API (the `/api/v1` surface is user/instance/workflow only), so these commands require username + password and cannot run on a deployment reachable only via JWT. This is now called out in the `query` and `instance` Skill references; the transport is selected per region, and each command uses REST where Archery exposes it.

## [1.0.10] - 2026-06-20

### Fixed

- **`instance list` (`--mode jwt`) now returns every instance, not just the first page.** The REST path (`listInstancesREST`) built the request as DRF *LimitOffset* (`?limit=&offset=&search=`) and read only `next != null` as `has_more` while discarding the `next` link, so it could never page past the first result set and silently ignored `--limit` / `--offset` / `--search`. But Archery's REST API is configured as `PageNumberPagination` (param `page`, `PAGE_SIZE` 5 upstream) with `DjangoFilterBackend` only (no `SearchFilter`) — so against more than 5 instances everything past page 1 was unreachable, which also broke any flow that discovers an instance ID before calling `instance resource` / `dict` / etc. The CLI now paginates via `page`, stops when the contract's `next` is null, filters `--search` client-side (DRF exposes no name-search param), and applies `--limit` / `--offset` client-side — mirroring the existing non-DBA session fallback `listUserAllInstancesSession`. When `--search` is unused the server's `count` is used as the true total (early-stopping once the requested window is filled); when it is used the matched count is the total. A page-walk safety cap guards against a misbehaving paginator. Session mode (the default) was never affected — it uses the Django AJAX `POST /instance/list/`, which genuinely honors `limit`/`offset`. Reproduced and verified live against `hhyo/archery:v1.8.5` in `--mode jwt` (8 instances across 2 pages: full enumeration, client-side `--limit`/`--offset` window, and cross-page `--search` all correct). Fixes #12.

## [1.0.9] - 2026-06-17

### Fixed

- **`instance list` works for non-DBA users.** It posted to `/instance/list/`, which Archery gates behind the DBA permission `sql.menu_instance_list`, so an ordinary user who can still *query* instances (granted via a resource group) got `403 permission denied` — even though the web SQL-query page lists those instances fine. On 403 the CLI now falls back to the ungated, user-scoped `/group/user_all_instances/` (the endpoint the web query page actually uses), returning the instances the user is authorized to use. That endpoint exposes only `{id, type, db_type, instance_name}` and has no server-side search, so host/port/credentials are omitted (with a stderr note) and `--search`/`--limit` are applied client-side. Verified on a local container with a non-DBA account in a resource group.
- **`workflow detail` no longer fails to parse executed workflows.** Archery returns `execute_time` (and `sequence`) on a ReviewResult row as a bare JSON number for some workflows and a string for others; the struct hard-typed them as `string`, so a numeric value failed the whole detail parse (`cannot unmarshal number into ... execute_time of type string`) and the SQL content could not be shown. These fields now accept either form. Reproduced and fixed against a real executed workflow.
- **Response-decode failures are no longer reported as retryable network errors.** A body the CLI received but could not parse (e.g. the `execute_time` mismatch above) surfaced as retryable `E_NETWORK` ("check host URL and network connectivity"); it now maps to non-retryable `E_UNKNOWN`, so an agent fixes the contract mismatch instead of blindly retrying.

### Notes

- These came out of an endpoint audit cross-referencing every CLI call against the Archery v1.8.5 source (route, permission decorator, request fields, response types). The audit also confirmed that `user list` (`/user/list/`) and `user resource-groups` (`/group/group/`) are `@superuser_required` upstream with no ungated user-scoped session endpoint, so they remain admin-only by Archery's design (the CLI degrades to `E_FORBIDDEN`); and that the workflow list/submit/review gates (`menu_sqlworkflow`, `sql_submit`, `sql_review`, `audit_user`) are legitimate feature permissions.

## [1.0.8] - 2026-06-17

### Fixed

- **2FA login now works against real Archery.** Session-mode login posted the OTP to `/api/v1/user/2fa/` (the 2FA *config* endpoint) instead of `/api/v1/user/2fa/verify/`, and never replayed the temp `session_key` from `/authenticate/` as the `sessionid` cookie — so the verify view could not find the password-verified user and rejected every code as wrong/expired, even when correct. The OTP is now posted to `/api/v1/user/2fa/verify/` with the temp session replayed as the `sessionid` cookie, matching Archery's real handshake. Verified live against `hhyo/archery:v1.8.5` (correct code logs in and caches the session; a wrong code returns the server's `验证码不正确！`). The prior unit test was self-fulfilling (its mock accepted exactly what the client sent); it now faithfully encodes the real contract — correct endpoint plus replayed session cookie — so a regression on either half fails the test.

## [1.0.7] - 2026-06-16

### Fixed

- npm `optionalDependencies` platform-package pins now match the package version. The previous release bumped the top-level version but left the pins at the prior version, so `npm install` resolved a stale platform binary (the new wrapper with the old binary). The publish workflow now rewrites `optionalDependencies` from the package version before `npm publish`, so the pins can no longer drift from the single source of truth.

## [1.0.6] - 2026-06-16

### Changed

- `update` now verifies the release Sigstore signature on `checksums.txt` **in-process** (embedded `sigstore-go`, bootstrapped from the embedded TUF trust root) instead of shelling out to an external `cosign`. Verification is mandatory and fail-closed: a missing signature bundle, a signature that does not verify against this repo's tagged release-workflow identity, or a checksum mismatch all refuse the update — there is no skip path. Releases are now signed with `cosign sign-blob --new-bundle-format`.

### Security

- Release-integrity failures (missing/invalid signature or checksum mismatch) now return the non-retryable `E_INTEGRITY` error code (exit 1) instead of a retryable network code, so an agent treats a possible supply-chain issue as a hard stop rather than retrying.

## [1.0.5] - 2026-06-16

### Added

- **`--read-only` global switch (and `ARCHERY_CLI_READONLY` env var).** When set, every write command is refused at the shared write chokepoint with an `E_FORBIDDEN` envelope (exit 4) — before the dry-run preview or any network call. Read commands are unaffected. This lets an agent run safely against production with writes hard-disabled regardless of `--dry-run`/`--confirm`. `doctor` and `context` report the current read-only state.
- **2FA detection + `--otp` (and `ARCHERY_CLI_OTP`).** Session login now understands Archery's two-factor flow. If an account has 2FA enabled, `/authenticate/` accepts the password but does not log in (it returns a `session_key` in `data` with no `sessionid` cookie); the CLI now detects this:
  - With `--otp <6-digit code>` it completes the login via `POST /api/v1/user/2fa/` and caches the resulting session, so **subsequent commands need no OTP** until the session expires.
  - Without an OTP it fails fast with the new **`E_2FA_REQUIRED`** code (exit 9, "human required") and a clear hint to retry with `--otp`, instead of the old opaque "session login failed".
  - 2FA codes are ~30s-lived: generate the code immediately before running. The cached `sessionid` (keyring) is reused afterwards, so OTP is a one-time cost per session.

### Changed

- **`workflow detail` reworked to surface execution results.** Detail now reads the execution/review result rows from `/sqlworkflow/detail_content/` and exposes them as `result[]` (each row carries `stage`, `stageStatus`, `errLevel`, `error`, `affectedRows`, `executeTime`), plus the string `statusCode` (e.g. `workflow_exception`) backfilled from the workflow list. An agent can now see **why an execution failed** (e.g. `Invalid remote backup information`) straight from the CLI instead of opening the web UI. SQL content is reconstructed from the rows, with inception wrapper statements filtered out.
- **Unified instance/group flags for `workflow submit` / `sqlcheck` / `auto-review`.** Session mode now also accepts the numeric `--instance` (ID) and `--group` (ID), resolving them to names via the instance/resource-group lists — the same flags JWT mode already used, so agents no longer have to remember a different flag set per transport. The name flags (`--instance-name` / `--group-name`) still work and win when both are given; an unknown ID fails with a clear `E_NOT_FOUND`.
- **Skill schema-discovery guidance.** The Skill now steers agents to locate tables/columns by **meaning** via `dict tables` / `dict table-info` (which carry table and column comments) instead of `instance resource` / `instance describe` (bare names only), with an explicit "stop guessing, ask the user" rule when no matching column exists. Fixes all `dict` examples that omitted the required `--db-type`, which fail with `Instance.DoesNotExist` on Archery v1.8.5.

### Notes

- **DDL execution and backups.** Executing a DDL workflow with backup enabled needs Archery's backup feature configured (`enable_backup_switch` on and a reachable backup database). On environments without it, execution fails with `Invalid remote backup information` — this is an Archery configuration prerequisite, not a CLI bug. Either have a DBA configure the backup database, or submit with backup disabled (`--backup=false`).

## [1.0.4] - 2026-06-16

### Added

- **Dual-mode transport — default `session`, REST `jwt` optional.** Login and every request now run over Archery's web (session + AJAX) endpoints by default, so a **plain developer account** can drive the CLI. REST + JWT is kept as an opt-in advanced mode via `--mode jwt` (a persistent flag) or a region's `mode` config. Mode precedence is `--mode` flag → region config → `session` default.
  - **Why:** Archery's `/api/v1` REST surface is closed to ordinary accounts (`403`) and is not enabled on older or most real-world deployments. The web session path (`/authenticate/`, `/sqlworkflow_list/`, `/sqlquery/`, …) works on **all versions and for ordinary accounts**, which is what most users actually have.
  - **Live-verified** against a local `hhyo/archery:v1.8.5` container with an ordinary (`cli_verify`) account: commands that return `403` under REST — `workflow list`, `instance list`, `user list`, `query log`, and others — all return `200` with real data over the session path.
- **New operational commands** (learned from real Archery ops Skills), all supported on the session path:
  - `instance create-db` — create a database on an instance.
  - `instance create-user` — create a database account.
  - `instance grant` — grant/revoke privileges (`--op grant|revoke`).
  - `instance test-instance` — connectivity probe.
  - `user resourcegroup-add` — add users/instances to a resource group.
  - `workflow audit-list` — list workflows awaiting audit.
  - `workflow auto-review` — classify SQL by the auto-review rules and optionally approve compliant workflows.

### Changed

- All 54 existing business commands now resolve over the session path by default; the JWT/REST path remains available unchanged via `--mode jwt`.

## [1.0.3] - 2026-06-15

### Added

- **Batch operations (CLI-SPEC §15).** Write workflows that act on many objects at once now run as a single agent-facing command — one envelope, one confirm token over the whole resolved set, one aggregated `items[]` + `summary{total,succeeded,failed,skipped}` — instead of a loop the agent has to drive:
  - `query run --instances a,b,c` runs one SQL across many instances, grouped per instance, failing soft (`--continue-on-error`, default `true`). The legacy `--instance` singular flag is kept as a compatibility alias.
  - `instance import --file <csv|json>` batch-onboards instances from a manifest (format inferred from the extension or `--manifest-format`), creating each via the existing single-create payload.
  - `workflow audit --ids 1,2,3` and `workflow execute --ids 1,2` batch-process workflows. `execute` is more conservative than the generic contract: the `--dangerous` gate is required and `--continue-on-error` defaults to `false` (stop at the first failure, report the unattempted remainder as `skipped`).
  - All four are **class B** client-side loops — Archery's upstream has no native bulk write endpoint, so results are **not** atomic and partial failures do not roll back already-applied items. The external contract (plural inputs, dry-run summary, single single-use confirm token, dangerous gate, per-item aggregation) is identical to a native batch.
  - `reference` gains a `batch_result` output schema plus runnable plural-input examples for each batch command.

### Fixed

- **`workflow execute` sent an incomplete payload.** It POSTed only `{workflow_id, mode}`, but Archery's `ExecuteWorkflowSerializer` requires `workflow_type` and — for SQL-review workflows — `engineer`, so every execute was rejected with `workflow_type 该字段是必填项`. The request now carries `workflow_type` (default `2`, SQL上线申请) and `engineer` (the authenticated user); both the single-target and `--ids` batch paths are fixed. Verified live on a real Archery stack (workflows reached `workflow_finish`).

### Changed

- npm scope 迁移 `@fatecannotbealtered-` → `@fateforge`（无横线 org 在 npm 被占，迁移到 `@fateforge`）. Updated the root package name, all platform `optionalDependencies`, the lockfile, the `update` command's `updateNPMPackage` constant, the README install commands and npm badges, and the platform-package generation script. The GitHub org / Go module path (`github.com/fatecannotbealtered/...`) and the `npx skills add fatecannotbealtered/...` source are unchanged.

## [1.0.2] - 2026-06-14

### Added

- `reference` now exposes a real per-command `output_schema` (label → fields/untrusted catalog) and a runnable `examples[]`, guarded against regression.

### Changed

- Confirm tokens are now single-use: replaying a confirmed write's token returns `E_CONFLICT` (safe-retry).

## [1.0.1] - 2026-06-14

### Added

- Offset-paginated list commands (`archive`, `slowquery`, `query`) now return an explicit `offset` echo and a `next_offset` cursor (present only when more rows remain), so an agent can page deterministically instead of re-deriving `offset + count` from `has_more`.

### Fixed

- Corrected `PrintErrorJSON`/`PrintErrorJSONWithCode` docstrings: the JSON failure envelope is emitted on **stdout** (the single document agents parse, per CLI-SPEC §4), which the code already did — only the comments wrongly said stderr.

## [1.0.0] - 2026-06-14

First stable release: recorded live smoke against a real Archery v1.8.5 stack (`docs/LIVE-SMOKE-EVIDENCE.md`); `release_readiness` is now `stable` with `live_smoke_status: verified`.

### Fixed
- **Session-mode commands never authenticated.** `dict`, `diagnostic`, `query`, `slowquery`, `binlog`, and `archive` use Archery's legacy Django endpoints, which need a session cookie — but the client only sent a JWT Bearer, so every request was redirected to `/login/` and the HTML page was parsed as JSON. `LoginWithSession` existed but was never called. Now `ensureSession` performs the Django form login lazily (GET `/login/` for the csrftoken, POST `/authenticate/` with `csrfmiddlewaretoken` + credentials), wired into `internalRequest`, and unsafe methods carry the `X-CSRFToken` header. Credentials come from the resolved region (env vars when the keyring holds only a JWT). Found by live smoke.
- **`instance create` sent `type` as an integer.** Archery's `Instance.type` is a `CharField` with choices `('master','slave')`; the API rejected `type 0`. Now sends the string.
- **`instance_tag` typed as `string`.** It is a ManyToMany relation serialized as an array, so `instance list`/`describe` failed to parse against a real server. Now `[]any`.

### Added
- Boundary test suite (`test/e2e`): all 55 leaf commands now execute through the real binary against a universal mock Archery upstream — read commands assert the ok envelope, write commands assert the dry-run `confirm_token`, plus full dry-run→confirm cycle, cross-machine token rejection, missing-credential, and usage-error paths.
- Command-level `update --check` test against a mock GitHub releases API.
- FCC enumeration guard (`TestFCC_EveryLeafCommandHasTest`): enumerates every leaf command from live `reference` output and asserts each has a command-level test; skips while `fcc_status` is honestly declared non-verified, so the claim cannot be flipped without coverage.
- Initial unreleased implementation.
- Multi-region support (cn/overseas).
- JWT authentication via /api/auth/token/.
- Workflow management (list, submit, detail, audit, execute, cancel, sqlcheck).
- SQL query execution and management.
- Instance management (CRUD, resources, describe, users).
- Slow query analysis and optimization.
- Database diagnostics (process, kill, tablespace, locks, transactions).
- Binlog management (list, parse, purge).
- Data archiving (list, apply, audit, switch, once, log).
- Data dictionary (tables, views, triggers, procedures, export).
- User and group management.
- Self-description commands (reference, doctor, context, changelog, update).
- Audit logging for write operations.
- _untrusted tagging for external content.
- npm wrapper distribution scaffolding.
- Agent-facing conformance checks for write confirmation, risk metadata, ID normalization, and URL validation.
- Repository governance files for AI-native open-source distribution: `AGENTS.md`, `NOTICE.md`, `CODE_OF_CONDUCT.md`, `docs/E2E.md`, and Dependabot configuration.
- npm lockfile and CI npm audit coverage.

### Changed
- Synced `.agent/` SEC-SPEC from the template: credential-at-rest is now the keyring three-part pattern (password discarded after login / secrets in the OS keyring / zero-secret config), file encryption demoted to a visible fallback, env vars as the recommended secret channel, and an honest note on Windows `0600` semantics.
- `release_readiness` is back to `beta` with `fcc_status: verified` — now machine-backed by the enumeration guard over the new boundary suite instead of hand-declared.
- `release_readiness` now declares `unpublishable` with `fcc_status: missing`: existing cmd tests inspect cobra flag definitions instead of executing commands, so CLI-boundary coverage is absent. `doctor` reports the matching `fail` with an actionable fix.
- In JSON mode the failure envelope is now the single JSON document on stdout, matching CLI-SPEC §4; stderr stays a human-readable side channel.
- Synced the `.agent/` spec copies from the ai-native-cli-spec template: stdout failure envelope (§4), HMAC confirm-token requirement (§7), signature_status/signature_verified fields (§14), Skill frontmatter `version` rule.
- Unified the golangci-lint v2 toolchain: Makefile installs from the `/v2` module path and CI uses `golangci-lint-action@v8` to match the v2 config format.
- Write commands now consistently require `--dry-run` followed by `--confirm <confirm_token>`, including authentication, query execution/favorite, workflow, instance, archive, binlog, diagnostic, and self-update writes.
- `auth login` now uses explicit `--url` in non-interactive JSON mode and validates HTTPS by default, allowing HTTP only for loopback development URLs.
- Agent-facing JSON output normalizes IDs to strings and tags common external/generated content fields with `_untrusted`.
- Self-update now syncs the whole Agent Skill directory through `npx skills add fatecannotbealtered/archery-cli -y -g` and reports `skill_sync_status`.
- Skill, README, `.agent/` specs, and reference docs now point agents to `reference` as the machine truth and document the current confirmation flow.

### Security
- Confirm tokens are now signed with a machine-local HMAC key (`confirm.secret`, created on first use with 0600 permissions) so they cannot be fabricated without running `--dry-run` on the same machine.
- Confirm tokens bind command path, operation payload, region, and username context; dry-run previews redact secrets while confirmation tokens bind the full payload.
- High and critical write commands now require explicit `--dangerous` in both dry-run and confirm steps as the T2 second gate.
- Release checksums are signed with Sigstore/Cosign, and install/update paths report signature verification status separately from checksum verification.
- npm installer checksum verification hard-fails when integrity cannot be verified.
- Credential persistence now uses OS keyring-only storage; passwords and tokens are never written to the config file.

### Fixed

- `update` text output now reads the snake_case result fields, so version numbers render instead of empty strings; removed duplicated camelCase keys (`currentVersion`, `targetVersion`, `updateAvailable`, `releaseUrl`, `installMethod`, `pendingPath`) from the top-level update JSON in favor of the snake_case set.
