acumatica-cli 0.19.0__tar.gz → 0.25.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/PKG-INFO +113 -47
  2. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/README.md +112 -46
  3. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/pyproject.toml +7 -2
  4. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/pyproject.toml.orig +4 -1
  5. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/bootstrap.py +35 -20
  6. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/bootstrap_project.xml +313 -5
  7. acumatica_cli-0.25.2/src/acumatica_cli/cli.py +1897 -0
  8. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/client.py +35 -1
  9. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/config.py +96 -49
  10. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/extract.py +10 -1
  11. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/inventory.py +1 -1
  12. acumatica_cli-0.25.2/src/acumatica_cli/matrix.py +155 -0
  13. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/reconcile.py +10 -4
  14. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/run.py +69 -24
  15. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/seed.py +141 -37
  16. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/seed_catalog.yaml +103 -6
  17. acumatica_cli-0.25.2/src/acumatica_cli/snapshot_map.yaml +314 -0
  18. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/state.py +1 -1
  19. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/README.md +19 -8
  20. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/baseline/20-accounts.yaml +24 -0
  21. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/baseline/50-gl-preferences.yaml +4 -0
  22. acumatica_cli-0.25.2/src/acumatica_cli/templates/config/master/05-numbering-sequences.yaml +82 -0
  23. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/20-in-preferences.yaml +9 -0
  24. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/56-so-preferences.yaml +2 -0
  25. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/57-po-preferences.yaml +3 -0
  26. acumatica_cli-0.25.2/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +12 -0
  27. acumatica_cli-0.25.2/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +12 -0
  28. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/62-ca-preferences.yaml +4 -0
  29. acumatica_cli-0.25.2/src/acumatica_cli/templates/config/master/90-roles.yaml +214 -0
  30. acumatica_cli-0.25.2/src/acumatica_cli/templates/config/master/91-users.yaml +16 -0
  31. acumatica_cli-0.25.2/src/acumatica_cli/templates/env +15 -0
  32. acumatica_cli-0.25.2/src/acumatica_cli/templates/matrix +9 -0
  33. acumatica_cli-0.25.2/src/acumatica_cli/templates/overlays/README.md +51 -0
  34. acumatica_cli-0.25.2/src/acumatica_cli/templates/overlays/default-24.200.001/README.md +11 -0
  35. acumatica_cli-0.25.2/src/acumatica_cli/templates/overlays/default-24.200.001/scenario/30-build.yaml +116 -0
  36. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/scenario/10-seed-capital.yaml +3 -3
  37. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/scenario/20-buy.yaml +3 -3
  38. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/scenario/30-build.yaml +1 -1
  39. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/scenario/40-sell.yaml +4 -4
  40. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/tenant.py +21 -5
  41. acumatica_cli-0.19.0/src/acumatica_cli/cli.py +0 -1242
  42. acumatica_cli-0.19.0/src/acumatica_cli/snapshot_map.yaml +0 -167
  43. acumatica_cli-0.19.0/src/acumatica_cli/target.py +0 -94
  44. acumatica_cli-0.19.0/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +0 -5
  45. acumatica_cli-0.19.0/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +0 -5
  46. acumatica_cli-0.19.0/src/acumatica_cli/templates/env +0 -16
  47. acumatica_cli-0.19.0/src/acumatica_cli/templates/target +0 -7
  48. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/__init__.py +0 -0
  49. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/bootstrap_plugin.cs +0 -0
  50. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/firstlogin.py +0 -0
  51. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/models.py +0 -0
  52. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/output.py +0 -0
  53. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/baseline/10-subaccounts.yaml +0 -0
  54. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/baseline/40-ledger.yaml +0 -0
  55. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/baseline/60-ledger-company.yaml +0 -0
  56. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/baseline/90-uoms.yaml +0 -0
  57. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/bootstrap/company.yaml +0 -0
  58. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/bootstrap/credit-terms.yaml +0 -0
  59. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/bootstrap/features.yaml +0 -0
  60. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/10-reason-codes.yaml +0 -0
  61. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/30-availability-rules.yaml +0 -0
  62. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/40-posting-classes.yaml +0 -0
  63. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/50-warehouse.yaml +0 -0
  64. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/51-warehouse-locations.yaml +0 -0
  65. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/52-warehouse-defaults.yaml +0 -0
  66. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/53-tax-categories.yaml +0 -0
  67. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/54-item-classes.yaml +0 -0
  68. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/58-order-types.yaml +0 -0
  69. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/63-cash-account.yaml +0 -0
  70. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/64-payment-methods.yaml +0 -0
  71. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/65-statement-cycles.yaml +0 -0
  72. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/70-vendor-classes.yaml +0 -0
  73. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/71-customer-classes.yaml +0 -0
  74. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/75-vendors.yaml +0 -0
  75. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/76-customers.yaml +0 -0
  76. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/80-stock-items-parts.yaml +0 -0
  77. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/82-stock-items-kits.yaml +0 -0
  78. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/85-kit-specifications.yaml +0 -0
  79. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/setup/10-financial-year.yaml +0 -0
  80. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/setup/20-master-calendar.yaml +0 -0
  81. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/setup/30-open-periods.yaml +0 -0
  82. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/views/10-trial-balance.yaml +0 -0
  83. {acumatica_cli-0.19.0 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/gitignore +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: acumatica-cli
3
- Version: 0.19.0
3
+ Version: 0.25.2
4
4
  Summary: Acumatica ERP Config-as-Code: tenant provisioning, baseline config, and reference data
5
5
  Author: Konstantin Borovik
6
6
  Author-email: Konstantin Borovik <kb@lab5.ca>
@@ -39,21 +39,21 @@ uv tool install acumatica-cli
39
39
 
40
40
  acu config init --host erp.example.com my-erp
41
41
  cd my-erp # edit .env: set ACU_PASSWORD, ACU_TENANT
42
- # Default API pin = target.yaml default_api
42
+ # pin+where = matrix.yaml cell (default_api + base_url)
43
43
  # start from a brand-new empty tenant
44
44
 
45
- acu config check # read-only preflight (incl. target.yaml)
46
- acu tenant create --id 3 --login DEV # create the tenant + bootstrap it (needs SSH)
45
+ acu config check # read-only preflight (incl. matrix.yaml)
46
+ acu tenant create --login DEV # create + bootstrap (SSH; --id optional)
47
47
  # or hosted: acu --tenant DEV bootstrap
48
48
  acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup,master}/
49
49
  acu --tenant DEV run scenario/ # once capital → buy → build → sell
50
50
  acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
51
51
  acu --tenant DEV state # capture state/ trial-balance
52
- acu --tenant DEV run scenario/ # replay transaction scenarios
52
+ acu check --yes --tenant DEV # cold lifecycle create→apply→run→diff (leave tenant)
53
53
  ```
54
54
 
55
55
  Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
56
- See [docs/demo-seed.md](docs/demo-seed.md) for the entity map, once-guard, and apply-order notes.
56
+ See [docs/demo-seed.md](docs/demo-seed.md) for the entity map, once-guard, apply-order notes, NumberingSequence vs prefs `*NumberingID`, curated *Preferences field depth (V41), and Role/User + password seed rules.
57
57
 
58
58
  **Hosted Acumatica (no SSH):** the tenant already exists; set a blank `ACU_SSH=` in `.env` (scaffold omits the key — without it, acu defaults to `Administrator@<ACU_BASE_URL host>` for SSH boxes).
59
59
 
@@ -71,20 +71,23 @@ acu bootstrap --export AcuBootstrap.zip # import + publish on SM204505
71
71
  ## CLI map
72
72
 
73
73
  ```text
74
- acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
74
+ acu [--cell ID] [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
75
75
  [--username U] [--password P] [--version] [--completion [SHELL]]
76
76
 
77
77
  ├── tenant tenant CRUD (ac.exe over SSH — control plane)
78
78
  │ ├── list CompanyID, sign-in name, internal CD, type
79
- │ ├── create --id N --login NAME create + bootstrap; re-run to republish (SSH)
79
+ │ ├── create --login NAME [--id N] create + bootstrap; re-run to republish (SSH)
80
80
  │ │ [--type SalesDemo|T100|U100] [--parent N] [--hidden] [--no-init]
81
- ├── delete --id N [--yes] delete the tenant and its data, recycle app pool
81
+ │ omit --id next free CompanyID (max list + 1)
82
+ │ ├── delete --id N | --login NAME [--yes]
83
+ │ │ delete the tenant and its data, recycle app pool
82
84
  │ └── recycle [--yes] restart site app pool (tenant map + free API slots)
83
85
 
84
86
  ├── bootstrap [--export PATH] publish AcuBootstrap (REST); --export = offline zip
85
87
  ├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
86
88
  ├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
87
89
  ├── run [--dry-run] [FILES...] execute transaction scenario YAML (exit 1 on any miss)
90
+ ├── check [--all] [--yes] [--tenant L] cold lifecycle create→apply→run→diff; leave tenant (V47)
88
91
  ├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
89
92
  │ capture derived state into state/ (not seed)
90
93
  ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
@@ -96,14 +99,14 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
96
99
  ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
97
100
 
98
101
  └── config configuration ops
99
- ├── init [--host HOST] [DIR] scaffold full data repo (config/, scenario/, target.yaml)
102
+ ├── init [--host HOST] [DIR] scaffold full data repo (config/, scenario/, matrix.yaml)
100
103
  ├── show print the resolved config as a complete .env
101
- └── check [--strict] preflight: discovery, secrets, target, REST, endpoints, SSH
104
+ └── check [--strict] preflight: discovery, secrets, matrix, REST, endpoints, SSH
102
105
  ```
103
106
 
104
107
  `apply` and `diff` without FILES prefer `config/<name>/` when any seed child exists under `config/`; otherwise root `bootstrap/`, `baseline/`, `setup/`, then `master/` when present.
105
108
  A path like `config/` expands nested seed dirs in that fixed order.
106
- `run` without FILES defaults to `scenario/`.
109
+ `run` without FILES defaults to `scenario/`. Scenario YAML may use `${current_period}` (host-local `MMyyyy`) on steps, expect params, and once.present params; `config/views` / `state` keep Period pinned (see [docs/demo-seed.md](docs/demo-seed.md#period-token-current_period-vs-pinned-views)).
107
110
  `state` without FILES defaults to `config/views/`; writes go to `state/` (`--out`).
108
111
  `extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
109
112
  `inventory` is offline (no REST/SSH/password): SM203520 Settings XML ZIP or `ac.exe export xml` folder writes to `inventory/`.
@@ -111,7 +114,9 @@ A path like `config/` expands nested seed dirs in that fixed order.
111
114
  Optional `snapshot_map.yaml` (data-repo root or package defaults) maps DAC tables to catalog entities and normalizes join (pad-trim, key/field aliases, Account/Sub FK CD resolve, enum label to code).
112
115
  See [docs/demo-seed.md](docs/demo-seed.md).
113
116
  `acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
114
- Run `acu <command> --help` for details on any command.
117
+ Run `acu --help` for the full mental model (workflow, planes, exit codes,
118
+ command map) — enough for an agent to learn the tool without extra docs.
119
+ Run `acu <command> --help` (or `-h`) for flags, examples, and prerequisites.
115
120
 
116
121
  ### Dual readers, one writer
117
122
 
@@ -133,27 +138,28 @@ See [docs/ac-exe.md](docs/ac-exe.md) for export / SM203520 notes and [docs/demo-
133
138
  ## The data repo
134
139
 
135
140
  Your configuration lives in its own git repo.
136
- `acu config init` scaffolds a **single full seed** under `config/` (Bootstrap `project.xml` at `Bootstrap/1.0.0`, expanded COA, masters) plus lifecycle `scenario/`, observer `config/views/`, and README.
141
+ `acu config init` scaffolds a **single full seed** under `config/` (features, company, credit terms, expanded COA, masters) plus lifecycle `scenario/`, observer `config/views/`, and README.
142
+ The Bootstrap endpoint contract is package SoT (`bootstrap_project.xml` inside the CLI — `Bootstrap/1.4.0`); `config init` never writes `project.xml`, and data repos must not keep one (a present file hard-errors on bootstrap/publish).
137
143
  There is no `--flavor`.
138
144
 
139
145
  | Path | What it holds |
140
146
  | ---- | ------------- |
141
- | `config/bootstrap/` | virgin-tenant config: features, company, credit terms, `project.xml` |
147
+ | `config/bootstrap/` | virgin-tenant config: features, company, credit terms (no `project.xml`) |
142
148
  | `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs |
143
149
  | `config/setup/` | one-time actions: financial year, master calendar, open periods |
144
- | `config/master/` | inventory/distribution masters (prefs, warehouse, items, parties) |
150
+ | `config/master/` | inventory/distribution masters: numbering (`05-…`) before prefs, warehouse, items, parties + Role/User (`90-roles` then `91-users`) |
145
151
  | `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
146
152
  | `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
147
153
  | `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
148
154
  | `inventory/` | engagement: offline snapshot tables from `acu inventory` (not seed; not SEED_DIRS) |
149
155
  | `findings/` | engagement: `acu reconcile` cross-check output (never apply path) |
150
- | `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
151
- | `.env` | where to apply and who signs in, every key an `ACU_*` variable |
156
+ | `matrix.yaml` | multi-host pin+where: cells `id`+`erp`+`default_api`+`base_url` (V27); `--cell` selects |
157
+ | `.env` | secrets + optional where override (`ACU_*`); never Default API pin |
152
158
 
153
159
  Legacy data repos may still keep root `bootstrap/`…`master/`; bare `apply`/`diff` prefer `config/` when present and never merge both trees.
154
160
 
155
161
  Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-`, and so on) encode dependency order.
156
- Commit `target.yaml` with the seeds so every clone knows the verified ERP line and Default API generation.
162
+ Commit `matrix.yaml` with the seeds so every clone knows verified ERP line, Default API half, and REST where per cell.
157
163
 
158
164
  Seed YAML is state: `apply` upserts it, `diff` proves it.
159
165
  `acu extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut).
@@ -174,11 +180,12 @@ Dual-served entities (on both Bootstrap and Default) need an explicit `endpoint:
174
180
  | Value | Resolves to |
175
181
  | ----- | ----------- |
176
182
  | omitted | `Default/<api_version>` for Default-only entities |
177
- | `bootstrap` | active `Bootstrap/<ver>` from `bootstrap/project.xml` or the packaged contract |
183
+ | `bootstrap` | active `Bootstrap/<ver>` from the packaged contract only |
178
184
  | `default` | `Default/<api_version>` — tracks the resolved API version |
179
- | `Bootstrap/1.0.0` or `Default/25.200.001` | literal pin (ignores the resolved Default version) |
185
+ | `Bootstrap/1.4.0` or `Default/25.200.001` | literal pin (ignores the resolved Default version) |
180
186
 
181
- `api_version` resolves as `--api-version` flag, else `target.yaml` `default_api` when present, else code default `25.200.001` (never `ACU_API_VERSION` in `.env`).
187
+ `api_version` resolves as `--api-version` flag, else active `matrix.yaml` cell `default_api`, else code default `25.200.001` (never `ACU_API_VERSION` in `.env`).
188
+ `base_url` resolves as `--url`, else `ACU_BASE_URL`, else active cell `base_url`.
182
189
  Prefer symbolic `default` over a pinned `Default/25.200.001` so the seed tree travels with the dataset pin.
183
190
 
184
191
  ## Installation
@@ -190,25 +197,27 @@ uv tool install acumatica-cli
190
197
  ```
191
198
 
192
199
  `pipx install acumatica-cli` and `pip install acumatica-cli` work too.
193
- For the latest development version straight from the main branch:
200
+
201
+ Or clone and install editable for development:
194
202
 
195
203
  ```sh
196
- uv tool install git+https://github.com/kborovik/acumatica-cli.git
204
+ git clone https://github.com/kborovik/acumatica-cli.git
205
+ cd acumatica-cli
206
+ gmake install # editable install as a global uv tool
197
207
  ```
198
208
 
199
209
  Verify with `acu --version`.
200
210
 
201
211
  ## Configuration
202
212
 
203
- Everything lives in one `.env` file: *where* to apply and *who* signs in
204
- (`ACU_*` vars only).
205
- The Default contract API pin is **not** in `.env` — it
206
- lives in committed `target.yaml` (`default_api`).
213
+ Secrets live in one `.env` file (`ACU_*` vars). Non-secret **where** and the
214
+ Default contract pin live in committed `matrix.yaml` (cell `base_url` +
215
+ `default_api`). Optional `ACU_BASE_URL` overrides cell where for ad-hoc probes.
207
216
 
208
217
  ```sh
209
- ACU_BASE_URL=http://acu-dev1.vm.internal/AcumaticaERP # required: REST root
218
+ # ACU_BASE_URL optional when matrix.yaml cell carries base_url
210
219
  ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
211
- # ACU_SSH omitted → defaults to Administrator@acu-dev1.vm.internal
220
+ # ACU_SSH omitted → defaults to Administrator@ + resolved base_url host
212
221
  # ACU_SSH= # hosted opt-out (blank key)
213
222
  ACU_USER=admin # optional, defaults to admin
214
223
  ACU_PASSWORD=... # required for live commands
@@ -218,34 +227,80 @@ There is no `ACU_API_VERSION` env key (unknown `ACU_*` vars are ignored).
218
227
  Ad-hoc override: `acu --api-version 24.200.001 …` (version half only, never
219
228
  `Default/25.200.001` — a full path would nest as `/entity/Default/Default/...`).
220
229
 
221
- The committed `target.yaml` next to `.env` declares the verified matrix (what, not where) and is the **sole data-repo Default pin**:
230
+ Committed `matrix.yaml` is the **sole data-repo pin+where registry** (1..N cells):
222
231
 
223
232
  ```yaml
224
- erp: "26.101.0225" # claimed product line/build
225
- default_api: "25.200.001" # sources Instance.api_version when --api-version absent
233
+ cells:
234
+ - id: "default"
235
+ erp: "26.101.0225" # claimed product line/build
236
+ default_api: "25.200.001" # sources Instance.api_version when --api-version absent
237
+ base_url: "http://acu-dev1.vm.internal/AcumaticaERP"
226
238
  ```
227
239
 
228
- When `target.yaml` is present, live commands resolve `api_version` from
229
- `default_api` (source-merge). `acu config check` reports
230
- `ok target (api_version from default_api=…; erp=… claimed)`.
231
- Missing `target.yaml` only warns on check unless you pass `--strict` (then
232
- the code default `25.200.001` is used).
240
+ `--cell <id>` selects a cell (omit means first cell).
241
+ When present, live commands source `api_version` from cell `default_api` and
242
+ `base_url` from the cell when flag/env leave them unset.
243
+ `acu config check` reports `ok matrix (cell=...; api_version from default_api=...; ...)`.
244
+ Missing `matrix.yaml` only warns on `config check` unless you pass `--strict`.
245
+ `acu check` (lifecycle) **requires** matrix.
246
+
247
+ ### Multi-host matrix (V44)
248
+
249
+ One **trunk seed** in the data repo serves every host. Version fan-out is
250
+ **not** long-running product branches (`acu-25r1`, `acu-26r1`, …).
251
+
252
+ | Piece | Role |
253
+ | ----- | ---- |
254
+ | Trunk seed | Canonical `config/` + `scenario/` (newest supported matrix) |
255
+ | `matrix.yaml` cells | Each host: `id`+`erp`+`default_api`+`base_url`; `--cell` / `acu check --all` |
256
+ | Optional overlays | Surgical seed deltas keyed by Default half (e.g. `overlays/default-24.200.001/`) |
257
+ | OpenAPI | Live `acu schema` dump only (gitignored); never multi-version swagger trees in package or data repo |
258
+
259
+ **Overlays** live under `overlays/default-<default_api>/` (scaffolded by
260
+ `acu config init`). No `--overlay` flag.
261
+
262
+ **Bare compose (pin auto):** when path args are omitted, `acu apply` /
263
+ `acu diff` append overlay config seed dirs when present; `acu run` replaces
264
+ same-basename scenario files from the pin overlay. Pin =
265
+ resolved `api_version` (matrix cell `default_api`). Explicit path args
266
+ disable auto-compose.
267
+
268
+ ```sh
269
+ # matrix cell default_api: 24.200.001 → uses overlays/default-24.200.001/
270
+ acu --cell lab25 apply
271
+ acu --cell lab25 run
272
+ acu --cell lab25 diff
273
+
274
+ # explicit path args (no auto) — later path wins same keys
275
+ acu apply config/ overlays/default-24.200.001/
276
+ acu diff config/ overlays/default-24.200.001/
277
+
278
+ # cold lifecycle every cell (SSH + tenant required); tenants left for inspect
279
+ acu check --all --yes --tenant LAB5
280
+ ```
281
+
282
+ Add a future half by creating `overlays/default-<new-half>/` with the
283
+ minimal rewrite; no long-running product branches and no multi-version
284
+ OpenAPI trees (V11/V44).
285
+
286
+ Sibling data-repo retirement of release branches:
287
+ [acumatica-gitops#2](https://github.com/kborovik/acumatica-gitops/issues/2).
233
288
 
234
289
  Worth knowing:
235
290
 
236
- - The file is found by walking up from the current directory, so any subdirectory of the data repo works.
237
- - Without a `.env`, global flags plus the process environment supply the full configuration.
238
- - When `ACU_SSH` is **absent**, acu defaults to `Administrator@` + the `ACU_BASE_URL` hostname.
291
+ - The `.env` file is found by walking up from the current directory, so any subdirectory of the data repo works.
292
+ - Without a `.env`, global flags plus the process environment (and matrix cell where) supply the configuration.
293
+ - When `ACU_SSH` is **absent**, acu defaults to `Administrator@` + the resolved base_url hostname.
239
294
  A **present blank** `ACU_SSH=` is the hosted opt-out.
240
- Only `acu tenant` requires a non-empty value post-default.
241
- - `acu config show` prints the resolved `.env` (password excluded; never `ACU_API_VERSION`) and comments `erp` / `default_api` plus the `api_version` source when `target.yaml` is present.
295
+ Only `acu tenant` / `acu check` require a non-empty value post-default.
296
+ - `acu config show` prints the resolved `.env` (password excluded; never `ACU_API_VERSION`) and comments active cell id/erp/default_api/base_url plus api_version source when `matrix.yaml` is present.
242
297
  - Redirect it to turn resolved state into a working config: `acu config show > .env`.
243
298
 
244
299
  Verify before touching anything live:
245
300
 
246
301
  ```sh
247
- acu config check # discovery, secrets, target, REST, endpoints, SSH
248
- acu config check --strict # missing target.yaml becomes fail
302
+ acu config check # discovery, secrets, matrix, REST, endpoints, SSH
303
+ acu config check --strict # missing matrix.yaml becomes fail
249
304
  acu apply --dry-run # show what would be written, write nothing
250
305
  ```
251
306
 
@@ -269,12 +324,23 @@ GitHub Actions runs the same gate on every push and pull request to `main`.
269
324
 
270
325
  ### Release
271
326
 
327
+ Human release notes live in root [`CHANGELOG.md`](CHANGELOG.md) (Keep a Changelog).
328
+ During development, append user-facing work under `## Unreleased` in `### Added` / `### Changed` / `### Fixed` as appropriate.
329
+ Empty Unreleased (no bullets) hard-fails the release — nothing to ship.
330
+
272
331
  ```sh
273
332
  gmake release patch # or minor | major
274
333
  ```
275
334
 
276
- Local release runs `gmake check`, bumps the version, commits, tags `v<version>`, and pushes.
277
- GitHub Actions re-runs the check on the tag, then publishes the GitHub release and PyPI package only if that check passes.
335
+ `gmake release` is the sole release path (never local `gh release create`):
336
+
337
+ 1. `gmake check` (ruff, basedpyright, offline pytest)
338
+ 2. Fail if `## Unreleased` has no bullets
339
+ 3. Bump `pyproject.toml` version (`major` | `minor` | `patch`)
340
+ 4. Promote Unreleased body to `## [vX.Y.Z] - YYYY-MM-DD`, leave an empty `## Unreleased`
341
+ 5. Commit `CHANGELOG.md` + `pyproject.toml` (+ lock if bumped) together, tag `vX.Y.Z`, push
342
+
343
+ GitHub Actions on tag `v*` re-runs CI, builds sdist+wheel, publishes to PyPI via OIDC trusted publishing, and creates a GitHub Release whose notes are the promoted CHANGELOG section for that tag (plus the artifacts).
278
344
 
279
345
  ### Live end-to-end tier
280
346
 
@@ -21,21 +21,21 @@ uv tool install acumatica-cli
21
21
 
22
22
  acu config init --host erp.example.com my-erp
23
23
  cd my-erp # edit .env: set ACU_PASSWORD, ACU_TENANT
24
- # Default API pin = target.yaml default_api
24
+ # pin+where = matrix.yaml cell (default_api + base_url)
25
25
  # start from a brand-new empty tenant
26
26
 
27
- acu config check # read-only preflight (incl. target.yaml)
28
- acu tenant create --id 3 --login DEV # create the tenant + bootstrap it (needs SSH)
27
+ acu config check # read-only preflight (incl. matrix.yaml)
28
+ acu tenant create --login DEV # create + bootstrap (SSH; --id optional)
29
29
  # or hosted: acu --tenant DEV bootstrap
30
30
  acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup,master}/
31
31
  acu --tenant DEV run scenario/ # once capital → buy → build → sell
32
32
  acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
33
33
  acu --tenant DEV state # capture state/ trial-balance
34
- acu --tenant DEV run scenario/ # replay transaction scenarios
34
+ acu check --yes --tenant DEV # cold lifecycle create→apply→run→diff (leave tenant)
35
35
  ```
36
36
 
37
37
  Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
38
- See [docs/demo-seed.md](docs/demo-seed.md) for the entity map, once-guard, and apply-order notes.
38
+ See [docs/demo-seed.md](docs/demo-seed.md) for the entity map, once-guard, apply-order notes, NumberingSequence vs prefs `*NumberingID`, curated *Preferences field depth (V41), and Role/User + password seed rules.
39
39
 
40
40
  **Hosted Acumatica (no SSH):** the tenant already exists; set a blank `ACU_SSH=` in `.env` (scaffold omits the key — without it, acu defaults to `Administrator@<ACU_BASE_URL host>` for SSH boxes).
41
41
 
@@ -53,20 +53,23 @@ acu bootstrap --export AcuBootstrap.zip # import + publish on SM204505
53
53
  ## CLI map
54
54
 
55
55
  ```text
56
- acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
56
+ acu [--cell ID] [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
57
57
  [--username U] [--password P] [--version] [--completion [SHELL]]
58
58
 
59
59
  ├── tenant tenant CRUD (ac.exe over SSH — control plane)
60
60
  │ ├── list CompanyID, sign-in name, internal CD, type
61
- │ ├── create --id N --login NAME create + bootstrap; re-run to republish (SSH)
61
+ │ ├── create --login NAME [--id N] create + bootstrap; re-run to republish (SSH)
62
62
  │ │ [--type SalesDemo|T100|U100] [--parent N] [--hidden] [--no-init]
63
- ├── delete --id N [--yes] delete the tenant and its data, recycle app pool
63
+ │ omit --id next free CompanyID (max list + 1)
64
+ │ ├── delete --id N | --login NAME [--yes]
65
+ │ │ delete the tenant and its data, recycle app pool
64
66
  │ └── recycle [--yes] restart site app pool (tenant map + free API slots)
65
67
 
66
68
  ├── bootstrap [--export PATH] publish AcuBootstrap (REST); --export = offline zip
67
69
  ├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
68
70
  ├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
69
71
  ├── run [--dry-run] [FILES...] execute transaction scenario YAML (exit 1 on any miss)
72
+ ├── check [--all] [--yes] [--tenant L] cold lifecycle create→apply→run→diff; leave tenant (V47)
70
73
  ├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
71
74
  │ capture derived state into state/ (not seed)
72
75
  ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
@@ -78,14 +81,14 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
78
81
  ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
79
82
 
80
83
  └── config configuration ops
81
- ├── init [--host HOST] [DIR] scaffold full data repo (config/, scenario/, target.yaml)
84
+ ├── init [--host HOST] [DIR] scaffold full data repo (config/, scenario/, matrix.yaml)
82
85
  ├── show print the resolved config as a complete .env
83
- └── check [--strict] preflight: discovery, secrets, target, REST, endpoints, SSH
86
+ └── check [--strict] preflight: discovery, secrets, matrix, REST, endpoints, SSH
84
87
  ```
85
88
 
86
89
  `apply` and `diff` without FILES prefer `config/<name>/` when any seed child exists under `config/`; otherwise root `bootstrap/`, `baseline/`, `setup/`, then `master/` when present.
87
90
  A path like `config/` expands nested seed dirs in that fixed order.
88
- `run` without FILES defaults to `scenario/`.
91
+ `run` without FILES defaults to `scenario/`. Scenario YAML may use `${current_period}` (host-local `MMyyyy`) on steps, expect params, and once.present params; `config/views` / `state` keep Period pinned (see [docs/demo-seed.md](docs/demo-seed.md#period-token-current_period-vs-pinned-views)).
89
92
  `state` without FILES defaults to `config/views/`; writes go to `state/` (`--out`).
90
93
  `extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
91
94
  `inventory` is offline (no REST/SSH/password): SM203520 Settings XML ZIP or `ac.exe export xml` folder writes to `inventory/`.
@@ -93,7 +96,9 @@ A path like `config/` expands nested seed dirs in that fixed order.
93
96
  Optional `snapshot_map.yaml` (data-repo root or package defaults) maps DAC tables to catalog entities and normalizes join (pad-trim, key/field aliases, Account/Sub FK CD resolve, enum label to code).
94
97
  See [docs/demo-seed.md](docs/demo-seed.md).
95
98
  `acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
96
- Run `acu <command> --help` for details on any command.
99
+ Run `acu --help` for the full mental model (workflow, planes, exit codes,
100
+ command map) — enough for an agent to learn the tool without extra docs.
101
+ Run `acu <command> --help` (or `-h`) for flags, examples, and prerequisites.
97
102
 
98
103
  ### Dual readers, one writer
99
104
 
@@ -115,27 +120,28 @@ See [docs/ac-exe.md](docs/ac-exe.md) for export / SM203520 notes and [docs/demo-
115
120
  ## The data repo
116
121
 
117
122
  Your configuration lives in its own git repo.
118
- `acu config init` scaffolds a **single full seed** under `config/` (Bootstrap `project.xml` at `Bootstrap/1.0.0`, expanded COA, masters) plus lifecycle `scenario/`, observer `config/views/`, and README.
123
+ `acu config init` scaffolds a **single full seed** under `config/` (features, company, credit terms, expanded COA, masters) plus lifecycle `scenario/`, observer `config/views/`, and README.
124
+ The Bootstrap endpoint contract is package SoT (`bootstrap_project.xml` inside the CLI — `Bootstrap/1.4.0`); `config init` never writes `project.xml`, and data repos must not keep one (a present file hard-errors on bootstrap/publish).
119
125
  There is no `--flavor`.
120
126
 
121
127
  | Path | What it holds |
122
128
  | ---- | ------------- |
123
- | `config/bootstrap/` | virgin-tenant config: features, company, credit terms, `project.xml` |
129
+ | `config/bootstrap/` | virgin-tenant config: features, company, credit terms (no `project.xml`) |
124
130
  | `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs |
125
131
  | `config/setup/` | one-time actions: financial year, master calendar, open periods |
126
- | `config/master/` | inventory/distribution masters (prefs, warehouse, items, parties) |
132
+ | `config/master/` | inventory/distribution masters: numbering (`05-…`) before prefs, warehouse, items, parties + Role/User (`90-roles` then `91-users`) |
127
133
  | `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
128
134
  | `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
129
135
  | `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
130
136
  | `inventory/` | engagement: offline snapshot tables from `acu inventory` (not seed; not SEED_DIRS) |
131
137
  | `findings/` | engagement: `acu reconcile` cross-check output (never apply path) |
132
- | `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
133
- | `.env` | where to apply and who signs in, every key an `ACU_*` variable |
138
+ | `matrix.yaml` | multi-host pin+where: cells `id`+`erp`+`default_api`+`base_url` (V27); `--cell` selects |
139
+ | `.env` | secrets + optional where override (`ACU_*`); never Default API pin |
134
140
 
135
141
  Legacy data repos may still keep root `bootstrap/`…`master/`; bare `apply`/`diff` prefer `config/` when present and never merge both trees.
136
142
 
137
143
  Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-`, and so on) encode dependency order.
138
- Commit `target.yaml` with the seeds so every clone knows the verified ERP line and Default API generation.
144
+ Commit `matrix.yaml` with the seeds so every clone knows verified ERP line, Default API half, and REST where per cell.
139
145
 
140
146
  Seed YAML is state: `apply` upserts it, `diff` proves it.
141
147
  `acu extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut).
@@ -156,11 +162,12 @@ Dual-served entities (on both Bootstrap and Default) need an explicit `endpoint:
156
162
  | Value | Resolves to |
157
163
  | ----- | ----------- |
158
164
  | omitted | `Default/<api_version>` for Default-only entities |
159
- | `bootstrap` | active `Bootstrap/<ver>` from `bootstrap/project.xml` or the packaged contract |
165
+ | `bootstrap` | active `Bootstrap/<ver>` from the packaged contract only |
160
166
  | `default` | `Default/<api_version>` — tracks the resolved API version |
161
- | `Bootstrap/1.0.0` or `Default/25.200.001` | literal pin (ignores the resolved Default version) |
167
+ | `Bootstrap/1.4.0` or `Default/25.200.001` | literal pin (ignores the resolved Default version) |
162
168
 
163
- `api_version` resolves as `--api-version` flag, else `target.yaml` `default_api` when present, else code default `25.200.001` (never `ACU_API_VERSION` in `.env`).
169
+ `api_version` resolves as `--api-version` flag, else active `matrix.yaml` cell `default_api`, else code default `25.200.001` (never `ACU_API_VERSION` in `.env`).
170
+ `base_url` resolves as `--url`, else `ACU_BASE_URL`, else active cell `base_url`.
164
171
  Prefer symbolic `default` over a pinned `Default/25.200.001` so the seed tree travels with the dataset pin.
165
172
 
166
173
  ## Installation
@@ -172,25 +179,27 @@ uv tool install acumatica-cli
172
179
  ```
173
180
 
174
181
  `pipx install acumatica-cli` and `pip install acumatica-cli` work too.
175
- For the latest development version straight from the main branch:
182
+
183
+ Or clone and install editable for development:
176
184
 
177
185
  ```sh
178
- uv tool install git+https://github.com/kborovik/acumatica-cli.git
186
+ git clone https://github.com/kborovik/acumatica-cli.git
187
+ cd acumatica-cli
188
+ gmake install # editable install as a global uv tool
179
189
  ```
180
190
 
181
191
  Verify with `acu --version`.
182
192
 
183
193
  ## Configuration
184
194
 
185
- Everything lives in one `.env` file: *where* to apply and *who* signs in
186
- (`ACU_*` vars only).
187
- The Default contract API pin is **not** in `.env` — it
188
- lives in committed `target.yaml` (`default_api`).
195
+ Secrets live in one `.env` file (`ACU_*` vars). Non-secret **where** and the
196
+ Default contract pin live in committed `matrix.yaml` (cell `base_url` +
197
+ `default_api`). Optional `ACU_BASE_URL` overrides cell where for ad-hoc probes.
189
198
 
190
199
  ```sh
191
- ACU_BASE_URL=http://acu-dev1.vm.internal/AcumaticaERP # required: REST root
200
+ # ACU_BASE_URL optional when matrix.yaml cell carries base_url
192
201
  ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
193
- # ACU_SSH omitted → defaults to Administrator@acu-dev1.vm.internal
202
+ # ACU_SSH omitted → defaults to Administrator@ + resolved base_url host
194
203
  # ACU_SSH= # hosted opt-out (blank key)
195
204
  ACU_USER=admin # optional, defaults to admin
196
205
  ACU_PASSWORD=... # required for live commands
@@ -200,34 +209,80 @@ There is no `ACU_API_VERSION` env key (unknown `ACU_*` vars are ignored).
200
209
  Ad-hoc override: `acu --api-version 24.200.001 …` (version half only, never
201
210
  `Default/25.200.001` — a full path would nest as `/entity/Default/Default/...`).
202
211
 
203
- The committed `target.yaml` next to `.env` declares the verified matrix (what, not where) and is the **sole data-repo Default pin**:
212
+ Committed `matrix.yaml` is the **sole data-repo pin+where registry** (1..N cells):
204
213
 
205
214
  ```yaml
206
- erp: "26.101.0225" # claimed product line/build
207
- default_api: "25.200.001" # sources Instance.api_version when --api-version absent
215
+ cells:
216
+ - id: "default"
217
+ erp: "26.101.0225" # claimed product line/build
218
+ default_api: "25.200.001" # sources Instance.api_version when --api-version absent
219
+ base_url: "http://acu-dev1.vm.internal/AcumaticaERP"
208
220
  ```
209
221
 
210
- When `target.yaml` is present, live commands resolve `api_version` from
211
- `default_api` (source-merge). `acu config check` reports
212
- `ok target (api_version from default_api=…; erp=… claimed)`.
213
- Missing `target.yaml` only warns on check unless you pass `--strict` (then
214
- the code default `25.200.001` is used).
222
+ `--cell <id>` selects a cell (omit means first cell).
223
+ When present, live commands source `api_version` from cell `default_api` and
224
+ `base_url` from the cell when flag/env leave them unset.
225
+ `acu config check` reports `ok matrix (cell=...; api_version from default_api=...; ...)`.
226
+ Missing `matrix.yaml` only warns on `config check` unless you pass `--strict`.
227
+ `acu check` (lifecycle) **requires** matrix.
228
+
229
+ ### Multi-host matrix (V44)
230
+
231
+ One **trunk seed** in the data repo serves every host. Version fan-out is
232
+ **not** long-running product branches (`acu-25r1`, `acu-26r1`, …).
233
+
234
+ | Piece | Role |
235
+ | ----- | ---- |
236
+ | Trunk seed | Canonical `config/` + `scenario/` (newest supported matrix) |
237
+ | `matrix.yaml` cells | Each host: `id`+`erp`+`default_api`+`base_url`; `--cell` / `acu check --all` |
238
+ | Optional overlays | Surgical seed deltas keyed by Default half (e.g. `overlays/default-24.200.001/`) |
239
+ | OpenAPI | Live `acu schema` dump only (gitignored); never multi-version swagger trees in package or data repo |
240
+
241
+ **Overlays** live under `overlays/default-<default_api>/` (scaffolded by
242
+ `acu config init`). No `--overlay` flag.
243
+
244
+ **Bare compose (pin auto):** when path args are omitted, `acu apply` /
245
+ `acu diff` append overlay config seed dirs when present; `acu run` replaces
246
+ same-basename scenario files from the pin overlay. Pin =
247
+ resolved `api_version` (matrix cell `default_api`). Explicit path args
248
+ disable auto-compose.
249
+
250
+ ```sh
251
+ # matrix cell default_api: 24.200.001 → uses overlays/default-24.200.001/
252
+ acu --cell lab25 apply
253
+ acu --cell lab25 run
254
+ acu --cell lab25 diff
255
+
256
+ # explicit path args (no auto) — later path wins same keys
257
+ acu apply config/ overlays/default-24.200.001/
258
+ acu diff config/ overlays/default-24.200.001/
259
+
260
+ # cold lifecycle every cell (SSH + tenant required); tenants left for inspect
261
+ acu check --all --yes --tenant LAB5
262
+ ```
263
+
264
+ Add a future half by creating `overlays/default-<new-half>/` with the
265
+ minimal rewrite; no long-running product branches and no multi-version
266
+ OpenAPI trees (V11/V44).
267
+
268
+ Sibling data-repo retirement of release branches:
269
+ [acumatica-gitops#2](https://github.com/kborovik/acumatica-gitops/issues/2).
215
270
 
216
271
  Worth knowing:
217
272
 
218
- - The file is found by walking up from the current directory, so any subdirectory of the data repo works.
219
- - Without a `.env`, global flags plus the process environment supply the full configuration.
220
- - When `ACU_SSH` is **absent**, acu defaults to `Administrator@` + the `ACU_BASE_URL` hostname.
273
+ - The `.env` file is found by walking up from the current directory, so any subdirectory of the data repo works.
274
+ - Without a `.env`, global flags plus the process environment (and matrix cell where) supply the configuration.
275
+ - When `ACU_SSH` is **absent**, acu defaults to `Administrator@` + the resolved base_url hostname.
221
276
  A **present blank** `ACU_SSH=` is the hosted opt-out.
222
- Only `acu tenant` requires a non-empty value post-default.
223
- - `acu config show` prints the resolved `.env` (password excluded; never `ACU_API_VERSION`) and comments `erp` / `default_api` plus the `api_version` source when `target.yaml` is present.
277
+ Only `acu tenant` / `acu check` require a non-empty value post-default.
278
+ - `acu config show` prints the resolved `.env` (password excluded; never `ACU_API_VERSION`) and comments active cell id/erp/default_api/base_url plus api_version source when `matrix.yaml` is present.
224
279
  - Redirect it to turn resolved state into a working config: `acu config show > .env`.
225
280
 
226
281
  Verify before touching anything live:
227
282
 
228
283
  ```sh
229
- acu config check # discovery, secrets, target, REST, endpoints, SSH
230
- acu config check --strict # missing target.yaml becomes fail
284
+ acu config check # discovery, secrets, matrix, REST, endpoints, SSH
285
+ acu config check --strict # missing matrix.yaml becomes fail
231
286
  acu apply --dry-run # show what would be written, write nothing
232
287
  ```
233
288
 
@@ -251,12 +306,23 @@ GitHub Actions runs the same gate on every push and pull request to `main`.
251
306
 
252
307
  ### Release
253
308
 
309
+ Human release notes live in root [`CHANGELOG.md`](CHANGELOG.md) (Keep a Changelog).
310
+ During development, append user-facing work under `## Unreleased` in `### Added` / `### Changed` / `### Fixed` as appropriate.
311
+ Empty Unreleased (no bullets) hard-fails the release — nothing to ship.
312
+
254
313
  ```sh
255
314
  gmake release patch # or minor | major
256
315
  ```
257
316
 
258
- Local release runs `gmake check`, bumps the version, commits, tags `v<version>`, and pushes.
259
- GitHub Actions re-runs the check on the tag, then publishes the GitHub release and PyPI package only if that check passes.
317
+ `gmake release` is the sole release path (never local `gh release create`):
318
+
319
+ 1. `gmake check` (ruff, basedpyright, offline pytest)
320
+ 2. Fail if `## Unreleased` has no bullets
321
+ 3. Bump `pyproject.toml` version (`major` | `minor` | `patch`)
322
+ 4. Promote Unreleased body to `## [vX.Y.Z] - YYYY-MM-DD`, leave an empty `## Unreleased`
323
+ 5. Commit `CHANGELOG.md` + `pyproject.toml` (+ lock if bumped) together, tag `vX.Y.Z`, push
324
+
325
+ GitHub Actions on tag `v*` re-runs CI, builds sdist+wheel, publishes to PyPI via OIDC trusted publishing, and creates a GitHub Release whose notes are the promoted CHANGELOG section for that tag (plus the artifacts).
260
326
 
261
327
  ### Live end-to-end tier
262
328
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acumatica-cli"
3
- version = "0.19.0"
3
+ version = "0.25.2"
4
4
  description = "Acumatica ERP Config-as-Code: tenant provisioning, baseline config, and reference data"
5
5
  license = "PolyForm-Noncommercial-1.0.0"
6
6
  readme = "README.md"
@@ -32,6 +32,7 @@ build-backend = "uv_build"
32
32
  [dependency-groups]
33
33
  dev = [
34
34
  "basedpyright>=1.37.0",
35
+ "freezegun>=1.5.5",
35
36
  "pytest>=8",
36
37
  "ruff>=0.13.0",
37
38
  ]
@@ -101,7 +102,11 @@ convention = "google"
101
102
  known-first-party = ["acumatica_cli"]
102
103
 
103
104
  [tool.ruff.lint.per-file-ignores]
104
- "src/acumatica_cli/cli.py" = ["PLR0913"]
105
+ "src/acumatica_cli/cli.py" = [
106
+ "PLR0913",
107
+ "PLR0917",
108
+ "D301",
109
+ ]
105
110
  "tests/*" = [
106
111
  "D101",
107
112
  "D102",