acumatica-cli 0.18.2__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.18.2 → acumatica_cli-0.25.2}/PKG-INFO +114 -47
  2. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/README.md +113 -46
  3. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/pyproject.toml +7 -2
  4. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/pyproject.toml.orig +4 -1
  5. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/bootstrap.py +35 -20
  6. {acumatica_cli-0.18.2 → 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.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/client.py +36 -2
  9. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/config.py +96 -49
  10. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/extract.py +10 -1
  11. {acumatica_cli-0.18.2 → 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.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/reconcile.py +10 -4
  14. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/run.py +69 -24
  15. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/seed.py +141 -37
  16. {acumatica_cli-0.18.2 → 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.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/state.py +1 -1
  19. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/README.md +19 -8
  20. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/baseline/20-accounts.yaml +24 -0
  21. {acumatica_cli-0.18.2 → 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.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/20-in-preferences.yaml +9 -0
  24. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/56-so-preferences.yaml +2 -0
  25. {acumatica_cli-0.18.2 → 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.18.2 → 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.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/scenario/10-seed-capital.yaml +3 -3
  37. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/scenario/20-buy.yaml +3 -3
  38. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/scenario/30-build.yaml +1 -1
  39. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/scenario/40-sell.yaml +4 -4
  40. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/tenant.py +21 -5
  41. acumatica_cli-0.18.2/src/acumatica_cli/cli.py +0 -1221
  42. acumatica_cli-0.18.2/src/acumatica_cli/snapshot_map.yaml +0 -167
  43. acumatica_cli-0.18.2/src/acumatica_cli/target.py +0 -94
  44. acumatica_cli-0.18.2/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +0 -5
  45. acumatica_cli-0.18.2/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +0 -5
  46. acumatica_cli-0.18.2/src/acumatica_cli/templates/env +0 -16
  47. acumatica_cli-0.18.2/src/acumatica_cli/templates/target +0 -7
  48. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/__init__.py +0 -0
  49. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/bootstrap_plugin.cs +0 -0
  50. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/firstlogin.py +0 -0
  51. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/models.py +0 -0
  52. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/output.py +0 -0
  53. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/baseline/10-subaccounts.yaml +0 -0
  54. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/baseline/40-ledger.yaml +0 -0
  55. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/baseline/60-ledger-company.yaml +0 -0
  56. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/baseline/90-uoms.yaml +0 -0
  57. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/bootstrap/company.yaml +0 -0
  58. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/bootstrap/credit-terms.yaml +0 -0
  59. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/bootstrap/features.yaml +0 -0
  60. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/10-reason-codes.yaml +0 -0
  61. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/30-availability-rules.yaml +0 -0
  62. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/40-posting-classes.yaml +0 -0
  63. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/50-warehouse.yaml +0 -0
  64. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/51-warehouse-locations.yaml +0 -0
  65. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/52-warehouse-defaults.yaml +0 -0
  66. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/53-tax-categories.yaml +0 -0
  67. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/54-item-classes.yaml +0 -0
  68. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/58-order-types.yaml +0 -0
  69. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/63-cash-account.yaml +0 -0
  70. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/64-payment-methods.yaml +0 -0
  71. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/65-statement-cycles.yaml +0 -0
  72. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/70-vendor-classes.yaml +0 -0
  73. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/71-customer-classes.yaml +0 -0
  74. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/75-vendors.yaml +0 -0
  75. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/76-customers.yaml +0 -0
  76. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/80-stock-items-parts.yaml +0 -0
  77. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/82-stock-items-kits.yaml +0 -0
  78. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/master/85-kit-specifications.yaml +0 -0
  79. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/setup/10-financial-year.yaml +0 -0
  80. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/setup/20-master-calendar.yaml +0 -0
  81. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/setup/30-open-periods.yaml +0 -0
  82. {acumatica_cli-0.18.2 → acumatica_cli-0.25.2}/src/acumatica_cli/templates/config/views/10-trial-balance.yaml +0 -0
  83. {acumatica_cli-0.18.2 → 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.18.2
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,19 +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
84
+ │ └── recycle [--yes] restart site app pool (tenant map + free API slots)
82
85
 
83
86
  ├── bootstrap [--export PATH] publish AcuBootstrap (REST); --export = offline zip
84
87
  ├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
85
88
  ├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
86
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)
87
91
  ├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
88
92
  │ capture derived state into state/ (not seed)
89
93
  ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
@@ -95,14 +99,14 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
95
99
  ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
96
100
 
97
101
  └── config configuration ops
98
- ├── 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)
99
103
  ├── show print the resolved config as a complete .env
100
- └── check [--strict] preflight: discovery, secrets, target, REST, endpoints, SSH
104
+ └── check [--strict] preflight: discovery, secrets, matrix, REST, endpoints, SSH
101
105
  ```
102
106
 
103
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.
104
108
  A path like `config/` expands nested seed dirs in that fixed order.
105
- `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)).
106
110
  `state` without FILES defaults to `config/views/`; writes go to `state/` (`--out`).
107
111
  `extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
108
112
  `inventory` is offline (no REST/SSH/password): SM203520 Settings XML ZIP or `ac.exe export xml` folder writes to `inventory/`.
@@ -110,7 +114,9 @@ A path like `config/` expands nested seed dirs in that fixed order.
110
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).
111
115
  See [docs/demo-seed.md](docs/demo-seed.md).
112
116
  `acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
113
- 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.
114
120
 
115
121
  ### Dual readers, one writer
116
122
 
@@ -132,27 +138,28 @@ See [docs/ac-exe.md](docs/ac-exe.md) for export / SM203520 notes and [docs/demo-
132
138
  ## The data repo
133
139
 
134
140
  Your configuration lives in its own git repo.
135
- `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).
136
143
  There is no `--flavor`.
137
144
 
138
145
  | Path | What it holds |
139
146
  | ---- | ------------- |
140
- | `config/bootstrap/` | virgin-tenant config: features, company, credit terms, `project.xml` |
147
+ | `config/bootstrap/` | virgin-tenant config: features, company, credit terms (no `project.xml`) |
141
148
  | `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs |
142
149
  | `config/setup/` | one-time actions: financial year, master calendar, open periods |
143
- | `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`) |
144
151
  | `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
145
152
  | `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
146
153
  | `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
147
154
  | `inventory/` | engagement: offline snapshot tables from `acu inventory` (not seed; not SEED_DIRS) |
148
155
  | `findings/` | engagement: `acu reconcile` cross-check output (never apply path) |
149
- | `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
150
- | `.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 |
151
158
 
152
159
  Legacy data repos may still keep root `bootstrap/`…`master/`; bare `apply`/`diff` prefer `config/` when present and never merge both trees.
153
160
 
154
161
  Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-`, and so on) encode dependency order.
155
- 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.
156
163
 
157
164
  Seed YAML is state: `apply` upserts it, `diff` proves it.
158
165
  `acu extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut).
@@ -173,11 +180,12 @@ Dual-served entities (on both Bootstrap and Default) need an explicit `endpoint:
173
180
  | Value | Resolves to |
174
181
  | ----- | ----------- |
175
182
  | omitted | `Default/<api_version>` for Default-only entities |
176
- | `bootstrap` | active `Bootstrap/<ver>` from `bootstrap/project.xml` or the packaged contract |
183
+ | `bootstrap` | active `Bootstrap/<ver>` from the packaged contract only |
177
184
  | `default` | `Default/<api_version>` — tracks the resolved API version |
178
- | `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) |
179
186
 
180
- `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`.
181
189
  Prefer symbolic `default` over a pinned `Default/25.200.001` so the seed tree travels with the dataset pin.
182
190
 
183
191
  ## Installation
@@ -189,25 +197,27 @@ uv tool install acumatica-cli
189
197
  ```
190
198
 
191
199
  `pipx install acumatica-cli` and `pip install acumatica-cli` work too.
192
- For the latest development version straight from the main branch:
200
+
201
+ Or clone and install editable for development:
193
202
 
194
203
  ```sh
195
- 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
196
207
  ```
197
208
 
198
209
  Verify with `acu --version`.
199
210
 
200
211
  ## Configuration
201
212
 
202
- Everything lives in one `.env` file: *where* to apply and *who* signs in
203
- (`ACU_*` vars only).
204
- The Default contract API pin is **not** in `.env` — it
205
- 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.
206
216
 
207
217
  ```sh
208
- ACU_BASE_URL=http://acu-dev1.vm.internal/AcumaticaERP # required: REST root
218
+ # ACU_BASE_URL optional when matrix.yaml cell carries base_url
209
219
  ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
210
- # ACU_SSH omitted → defaults to Administrator@acu-dev1.vm.internal
220
+ # ACU_SSH omitted → defaults to Administrator@ + resolved base_url host
211
221
  # ACU_SSH= # hosted opt-out (blank key)
212
222
  ACU_USER=admin # optional, defaults to admin
213
223
  ACU_PASSWORD=... # required for live commands
@@ -217,34 +227,80 @@ There is no `ACU_API_VERSION` env key (unknown `ACU_*` vars are ignored).
217
227
  Ad-hoc override: `acu --api-version 24.200.001 …` (version half only, never
218
228
  `Default/25.200.001` — a full path would nest as `/entity/Default/Default/...`).
219
229
 
220
- 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):
221
231
 
222
232
  ```yaml
223
- erp: "26.101.0225" # claimed product line/build
224
- 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"
225
238
  ```
226
239
 
227
- When `target.yaml` is present, live commands resolve `api_version` from
228
- `default_api` (source-merge). `acu config check` reports
229
- `ok target (api_version from default_api=…; erp=… claimed)`.
230
- Missing `target.yaml` only warns on check unless you pass `--strict` (then
231
- 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).
232
288
 
233
289
  Worth knowing:
234
290
 
235
- - The file is found by walking up from the current directory, so any subdirectory of the data repo works.
236
- - Without a `.env`, global flags plus the process environment supply the full configuration.
237
- - 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.
238
294
  A **present blank** `ACU_SSH=` is the hosted opt-out.
239
- Only `acu tenant` requires a non-empty value post-default.
240
- - `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.
241
297
  - Redirect it to turn resolved state into a working config: `acu config show > .env`.
242
298
 
243
299
  Verify before touching anything live:
244
300
 
245
301
  ```sh
246
- acu config check # discovery, secrets, target, REST, endpoints, SSH
247
- 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
248
304
  acu apply --dry-run # show what would be written, write nothing
249
305
  ```
250
306
 
@@ -268,12 +324,23 @@ GitHub Actions runs the same gate on every push and pull request to `main`.
268
324
 
269
325
  ### Release
270
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
+
271
331
  ```sh
272
332
  gmake release patch # or minor | major
273
333
  ```
274
334
 
275
- Local release runs `gmake check`, bumps the version, commits, tags `v<version>`, and pushes.
276
- 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).
277
344
 
278
345
  ### Live end-to-end tier
279
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,19 +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
66
+ │ └── recycle [--yes] restart site app pool (tenant map + free API slots)
64
67
 
65
68
  ├── bootstrap [--export PATH] publish AcuBootstrap (REST); --export = offline zip
66
69
  ├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
67
70
  ├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
68
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)
69
73
  ├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
70
74
  │ capture derived state into state/ (not seed)
71
75
  ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
@@ -77,14 +81,14 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
77
81
  ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
78
82
 
79
83
  └── config configuration ops
80
- ├── 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)
81
85
  ├── show print the resolved config as a complete .env
82
- └── check [--strict] preflight: discovery, secrets, target, REST, endpoints, SSH
86
+ └── check [--strict] preflight: discovery, secrets, matrix, REST, endpoints, SSH
83
87
  ```
84
88
 
85
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.
86
90
  A path like `config/` expands nested seed dirs in that fixed order.
87
- `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)).
88
92
  `state` without FILES defaults to `config/views/`; writes go to `state/` (`--out`).
89
93
  `extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
90
94
  `inventory` is offline (no REST/SSH/password): SM203520 Settings XML ZIP or `ac.exe export xml` folder writes to `inventory/`.
@@ -92,7 +96,9 @@ A path like `config/` expands nested seed dirs in that fixed order.
92
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).
93
97
  See [docs/demo-seed.md](docs/demo-seed.md).
94
98
  `acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
95
- 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.
96
102
 
97
103
  ### Dual readers, one writer
98
104
 
@@ -114,27 +120,28 @@ See [docs/ac-exe.md](docs/ac-exe.md) for export / SM203520 notes and [docs/demo-
114
120
  ## The data repo
115
121
 
116
122
  Your configuration lives in its own git repo.
117
- `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).
118
125
  There is no `--flavor`.
119
126
 
120
127
  | Path | What it holds |
121
128
  | ---- | ------------- |
122
- | `config/bootstrap/` | virgin-tenant config: features, company, credit terms, `project.xml` |
129
+ | `config/bootstrap/` | virgin-tenant config: features, company, credit terms (no `project.xml`) |
123
130
  | `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs |
124
131
  | `config/setup/` | one-time actions: financial year, master calendar, open periods |
125
- | `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`) |
126
133
  | `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
127
134
  | `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
128
135
  | `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
129
136
  | `inventory/` | engagement: offline snapshot tables from `acu inventory` (not seed; not SEED_DIRS) |
130
137
  | `findings/` | engagement: `acu reconcile` cross-check output (never apply path) |
131
- | `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
132
- | `.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 |
133
140
 
134
141
  Legacy data repos may still keep root `bootstrap/`…`master/`; bare `apply`/`diff` prefer `config/` when present and never merge both trees.
135
142
 
136
143
  Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-`, and so on) encode dependency order.
137
- 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.
138
145
 
139
146
  Seed YAML is state: `apply` upserts it, `diff` proves it.
140
147
  `acu extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut).
@@ -155,11 +162,12 @@ Dual-served entities (on both Bootstrap and Default) need an explicit `endpoint:
155
162
  | Value | Resolves to |
156
163
  | ----- | ----------- |
157
164
  | omitted | `Default/<api_version>` for Default-only entities |
158
- | `bootstrap` | active `Bootstrap/<ver>` from `bootstrap/project.xml` or the packaged contract |
165
+ | `bootstrap` | active `Bootstrap/<ver>` from the packaged contract only |
159
166
  | `default` | `Default/<api_version>` — tracks the resolved API version |
160
- | `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) |
161
168
 
162
- `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`.
163
171
  Prefer symbolic `default` over a pinned `Default/25.200.001` so the seed tree travels with the dataset pin.
164
172
 
165
173
  ## Installation
@@ -171,25 +179,27 @@ uv tool install acumatica-cli
171
179
  ```
172
180
 
173
181
  `pipx install acumatica-cli` and `pip install acumatica-cli` work too.
174
- For the latest development version straight from the main branch:
182
+
183
+ Or clone and install editable for development:
175
184
 
176
185
  ```sh
177
- 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
178
189
  ```
179
190
 
180
191
  Verify with `acu --version`.
181
192
 
182
193
  ## Configuration
183
194
 
184
- Everything lives in one `.env` file: *where* to apply and *who* signs in
185
- (`ACU_*` vars only).
186
- The Default contract API pin is **not** in `.env` — it
187
- 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.
188
198
 
189
199
  ```sh
190
- ACU_BASE_URL=http://acu-dev1.vm.internal/AcumaticaERP # required: REST root
200
+ # ACU_BASE_URL optional when matrix.yaml cell carries base_url
191
201
  ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
192
- # ACU_SSH omitted → defaults to Administrator@acu-dev1.vm.internal
202
+ # ACU_SSH omitted → defaults to Administrator@ + resolved base_url host
193
203
  # ACU_SSH= # hosted opt-out (blank key)
194
204
  ACU_USER=admin # optional, defaults to admin
195
205
  ACU_PASSWORD=... # required for live commands
@@ -199,34 +209,80 @@ There is no `ACU_API_VERSION` env key (unknown `ACU_*` vars are ignored).
199
209
  Ad-hoc override: `acu --api-version 24.200.001 …` (version half only, never
200
210
  `Default/25.200.001` — a full path would nest as `/entity/Default/Default/...`).
201
211
 
202
- 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):
203
213
 
204
214
  ```yaml
205
- erp: "26.101.0225" # claimed product line/build
206
- 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"
207
220
  ```
208
221
 
209
- When `target.yaml` is present, live commands resolve `api_version` from
210
- `default_api` (source-merge). `acu config check` reports
211
- `ok target (api_version from default_api=…; erp=… claimed)`.
212
- Missing `target.yaml` only warns on check unless you pass `--strict` (then
213
- 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).
214
270
 
215
271
  Worth knowing:
216
272
 
217
- - The file is found by walking up from the current directory, so any subdirectory of the data repo works.
218
- - Without a `.env`, global flags plus the process environment supply the full configuration.
219
- - 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.
220
276
  A **present blank** `ACU_SSH=` is the hosted opt-out.
221
- Only `acu tenant` requires a non-empty value post-default.
222
- - `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.
223
279
  - Redirect it to turn resolved state into a working config: `acu config show > .env`.
224
280
 
225
281
  Verify before touching anything live:
226
282
 
227
283
  ```sh
228
- acu config check # discovery, secrets, target, REST, endpoints, SSH
229
- 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
230
286
  acu apply --dry-run # show what would be written, write nothing
231
287
  ```
232
288
 
@@ -250,12 +306,23 @@ GitHub Actions runs the same gate on every push and pull request to `main`.
250
306
 
251
307
  ### Release
252
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
+
253
313
  ```sh
254
314
  gmake release patch # or minor | major
255
315
  ```
256
316
 
257
- Local release runs `gmake check`, bumps the version, commits, tags `v<version>`, and pushes.
258
- 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).
259
326
 
260
327
  ### Live end-to-end tier
261
328
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acumatica-cli"
3
- version = "0.18.2"
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",