acumatica-cli 0.19.0__tar.gz → 0.26.0__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 (84) hide show
  1. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/PKG-INFO +185 -56
  2. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/README.md +184 -55
  3. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/pyproject.toml +7 -2
  4. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/pyproject.toml.orig +4 -1
  5. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/bootstrap.py +35 -20
  6. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/bootstrap_project.xml +344 -5
  7. acumatica_cli-0.26.0/src/acumatica_cli/cli.py +1897 -0
  8. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/client.py +35 -1
  9. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/config.py +100 -49
  10. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/extract.py +10 -1
  11. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/inventory.py +1 -1
  12. acumatica_cli-0.26.0/src/acumatica_cli/matrix.py +155 -0
  13. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/reconcile.py +10 -4
  14. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/run.py +69 -24
  15. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/seed.py +141 -37
  16. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/seed_catalog.yaml +117 -6
  17. acumatica_cli-0.26.0/src/acumatica_cli/snapshot_map.yaml +314 -0
  18. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/state.py +1 -1
  19. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/README.md +19 -8
  20. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/baseline/20-accounts.yaml +24 -0
  21. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/baseline/50-gl-preferences.yaml +4 -0
  22. acumatica_cli-0.26.0/src/acumatica_cli/templates/config/bootstrap/segmented-key.yaml +10 -0
  23. acumatica_cli-0.26.0/src/acumatica_cli/templates/config/master/05-numbering-sequences.yaml +82 -0
  24. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/20-in-preferences.yaml +9 -0
  25. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/56-so-preferences.yaml +2 -0
  26. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/57-po-preferences.yaml +3 -0
  27. acumatica_cli-0.26.0/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +12 -0
  28. acumatica_cli-0.26.0/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +12 -0
  29. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/62-ca-preferences.yaml +4 -0
  30. acumatica_cli-0.26.0/src/acumatica_cli/templates/config/master/90-roles.yaml +214 -0
  31. acumatica_cli-0.26.0/src/acumatica_cli/templates/config/master/91-users.yaml +16 -0
  32. acumatica_cli-0.26.0/src/acumatica_cli/templates/env +15 -0
  33. acumatica_cli-0.26.0/src/acumatica_cli/templates/matrix +9 -0
  34. acumatica_cli-0.26.0/src/acumatica_cli/templates/overlays/README.md +51 -0
  35. acumatica_cli-0.26.0/src/acumatica_cli/templates/overlays/default-24.200.001/README.md +11 -0
  36. acumatica_cli-0.26.0/src/acumatica_cli/templates/overlays/default-24.200.001/scenario/30-build.yaml +116 -0
  37. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/scenario/10-seed-capital.yaml +3 -3
  38. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/scenario/20-buy.yaml +3 -3
  39. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/scenario/30-build.yaml +1 -1
  40. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/scenario/40-sell.yaml +4 -4
  41. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/tenant.py +21 -5
  42. acumatica_cli-0.19.0/src/acumatica_cli/cli.py +0 -1242
  43. acumatica_cli-0.19.0/src/acumatica_cli/snapshot_map.yaml +0 -167
  44. acumatica_cli-0.19.0/src/acumatica_cli/target.py +0 -94
  45. acumatica_cli-0.19.0/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +0 -5
  46. acumatica_cli-0.19.0/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +0 -5
  47. acumatica_cli-0.19.0/src/acumatica_cli/templates/env +0 -16
  48. acumatica_cli-0.19.0/src/acumatica_cli/templates/target +0 -7
  49. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/__init__.py +0 -0
  50. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/bootstrap_plugin.cs +0 -0
  51. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/firstlogin.py +0 -0
  52. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/models.py +0 -0
  53. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/output.py +0 -0
  54. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/baseline/10-subaccounts.yaml +0 -0
  55. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/baseline/40-ledger.yaml +0 -0
  56. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/baseline/60-ledger-company.yaml +0 -0
  57. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/baseline/90-uoms.yaml +0 -0
  58. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/bootstrap/company.yaml +0 -0
  59. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/bootstrap/credit-terms.yaml +0 -0
  60. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/bootstrap/features.yaml +0 -0
  61. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/10-reason-codes.yaml +0 -0
  62. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/30-availability-rules.yaml +0 -0
  63. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/40-posting-classes.yaml +0 -0
  64. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/50-warehouse.yaml +0 -0
  65. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/51-warehouse-locations.yaml +0 -0
  66. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/52-warehouse-defaults.yaml +0 -0
  67. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/53-tax-categories.yaml +0 -0
  68. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/54-item-classes.yaml +0 -0
  69. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/58-order-types.yaml +0 -0
  70. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/63-cash-account.yaml +0 -0
  71. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/64-payment-methods.yaml +0 -0
  72. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/65-statement-cycles.yaml +0 -0
  73. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/70-vendor-classes.yaml +0 -0
  74. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/71-customer-classes.yaml +0 -0
  75. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/75-vendors.yaml +0 -0
  76. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/76-customers.yaml +0 -0
  77. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/80-stock-items-parts.yaml +0 -0
  78. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/82-stock-items-kits.yaml +0 -0
  79. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/master/85-kit-specifications.yaml +0 -0
  80. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/setup/10-financial-year.yaml +0 -0
  81. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/setup/20-master-calendar.yaml +0 -0
  82. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/setup/30-open-periods.yaml +0 -0
  83. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/src/acumatica_cli/templates/config/views/10-trial-balance.yaml +0 -0
  84. {acumatica_cli-0.19.0 → acumatica_cli-0.26.0}/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.26.0
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>
@@ -32,6 +32,33 @@ Acumatica configuration normally lives in the web UI: wizards, screens, and manu
32
32
 
33
33
  `acu` moves that configuration into YAML files in a git repo, so a tenant can be rebuilt from scratch, audited in a pull request, and checked for drift like any other infrastructure.
34
34
 
35
+ ## What this tool is capable of
36
+
37
+ YAML in git is the source of truth; the live tenant is the target. `apply` is the only writer.
38
+
39
+ | You can | Command | Effect |
40
+ | ------- | ------- | ------ |
41
+ | Seed a tenant from YAML | `apply` | Idempotent PUT of `config/` into the live tenant |
42
+ | Detect drift | `diff` | Compare seed YAML to live (exit 2 when they diverge) |
43
+ | Pull live config into YAML | `extract` | Inverse of `apply` — GET into `config/{bootstrap,baseline,setup,master}/` |
44
+ | Run transaction scripts | `run` | Forward documents from `scenario/` (capital, buy, build, sell) |
45
+ | Capture derived balances | `state` | Inquire trial-balance etc. into `state/` (not seed) |
46
+ | Create and destroy tenants | `tenant` | SSH control plane: list / create / delete / recycle |
47
+ | Publish the Bootstrap contract | `bootstrap` | REST publish of AcuBootstrap (or `--export` zip for the UI) |
48
+ | Prove a cold rebuild | `check` | create, then apply, then run, then diff on a fresh tenant |
49
+ | Snapshot a site offline | `inventory` | SM203520 XML ZIP or `ac.exe export xml` writes `inventory/` |
50
+ | Cross-check snapshot vs seed | `reconcile` | `inventory/` plus optional `config/` writes `findings/` only |
51
+ | Dump the contract schema | `schema` | OpenAPI `swagger.json` for the pinned endpoint |
52
+ | Scaffold a data repo | `config init` | Full `config/`, `scenario/`, `matrix.yaml` tree |
53
+ | Preflight a target | `config check` | Discovery, secrets, matrix, REST, endpoints, SSH |
54
+
55
+ Seed YAML covers features, company, credit terms, subaccounts, chart of accounts, ledger, UOMs, financial year / calendar / periods, numbering sequences, inventory and distribution masters, roles, and users.
56
+
57
+ REST is the data plane (`apply`, `diff`, `run`, `extract`, `state`, `bootstrap`, `schema`).
58
+ SSH is the control plane (`tenant *`).
59
+
60
+ Hosted sites skip SSH.
61
+
35
62
  ## Quick start
36
63
 
37
64
  ```sh
@@ -39,23 +66,24 @@ uv tool install acumatica-cli
39
66
 
40
67
  acu config init --host erp.example.com my-erp
41
68
  cd my-erp # edit .env: set ACU_PASSWORD, ACU_TENANT
42
- # Default API pin = target.yaml default_api
69
+ # pin+where = matrix.yaml cell (default_api + base_url)
43
70
  # start from a brand-new empty tenant
44
71
 
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)
72
+ acu config check # read-only preflight (incl. matrix.yaml)
73
+ acu tenant create --login DEV # create + bootstrap (SSH; --id optional)
47
74
  # or hosted: acu --tenant DEV bootstrap
48
75
  acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup,master}/
49
76
  acu --tenant DEV run scenario/ # once capital → buy → build → sell
50
77
  acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
51
78
  acu --tenant DEV state # capture state/ trial-balance
52
- acu --tenant DEV run scenario/ # replay transaction scenarios
79
+ acu check --yes --tenant DEV # cold lifecycle create→apply→run→diff (leave tenant)
53
80
  ```
54
81
 
55
82
  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.
83
+ 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
84
 
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).
85
+ **Hosted Acumatica (no SSH):** the tenant already exists; set a blank `ACU_SSH=` in `.env`.
86
+ The scaffold omits the key — without it, acu defaults to `Administrator@<ACU_BASE_URL host>` for SSH boxes.
59
87
 
60
88
  ```sh
61
89
  acu config init --host customer.acumatica.com my-erp
@@ -71,20 +99,23 @@ acu bootstrap --export AcuBootstrap.zip # import + publish on SM204505
71
99
  ## CLI map
72
100
 
73
101
  ```text
74
- acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
102
+ acu [--cell ID] [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
75
103
  [--username U] [--password P] [--version] [--completion [SHELL]]
76
104
 
77
105
  ├── tenant tenant CRUD (ac.exe over SSH — control plane)
78
106
  │ ├── list CompanyID, sign-in name, internal CD, type
79
- │ ├── create --id N --login NAME create + bootstrap; re-run to republish (SSH)
107
+ │ ├── create --login NAME [--id N] create + bootstrap; re-run to republish (SSH)
80
108
  │ │ [--type SalesDemo|T100|U100] [--parent N] [--hidden] [--no-init]
81
- ├── delete --id N [--yes] delete the tenant and its data, recycle app pool
109
+ │ omit --id next free CompanyID (max list + 1)
110
+ │ ├── delete --id N | --login NAME [--yes]
111
+ │ │ delete the tenant and its data, recycle app pool
82
112
  │ └── recycle [--yes] restart site app pool (tenant map + free API slots)
83
113
 
84
114
  ├── bootstrap [--export PATH] publish AcuBootstrap (REST); --export = offline zip
85
115
  ├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
86
116
  ├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
87
117
  ├── run [--dry-run] [FILES...] execute transaction scenario YAML (exit 1 on any miss)
118
+ ├── check [--all] [--yes] [--tenant L] cold lifecycle create→apply→run→diff; leave tenant (V47)
88
119
  ├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
89
120
  │ capture derived state into state/ (not seed)
90
121
  ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
@@ -96,22 +127,41 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
96
127
  ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
97
128
 
98
129
  └── config configuration ops
99
- ├── init [--host HOST] [DIR] scaffold full data repo (config/, scenario/, target.yaml)
130
+ ├── init [--host HOST] [DIR] scaffold full data repo (config/, scenario/, matrix.yaml)
100
131
  ├── show print the resolved config as a complete .env
101
- └── check [--strict] preflight: discovery, secrets, target, REST, endpoints, SSH
132
+ └── check [--strict] preflight: discovery, secrets, matrix, REST, endpoints, SSH
102
133
  ```
103
134
 
104
- `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
- A path like `config/` expands nested seed dirs in that fixed order.
106
- `run` without FILES defaults to `scenario/`.
107
- `state` without FILES defaults to `config/views/`; writes go to `state/` (`--out`).
135
+ When you omit FILES:
136
+
137
+ - `apply` / `diff` prefer `config/<name>/` if any seed child exists under `config/`.
138
+ Otherwise they walk root `bootstrap/`, `baseline/`, `setup/`, then `master/` when present.
139
+ A path like `config/` expands those nested seed dirs in the same order.
140
+ - `run` defaults to `scenario/`.
141
+ - `state` defaults to `config/views/`; writes go to `state/` (`--out`).
142
+
143
+ Scenario YAML may use `${current_period}` (host-local `MMyyyy`) on steps, expect params, and `once.present` params.
144
+ `config/views` and `state` keep Period pinned — see [docs/demo-seed.md](docs/demo-seed.md#period-token-current_period-vs-pinned-views).
145
+
108
146
  `extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
109
- `inventory` is offline (no REST/SSH/password): SM203520 Settings XML ZIP or `ac.exe export xml` folder writes to `inventory/`.
110
- `reconcile` is offline: compare `inventory/` to optional `config/` and write `findings/` only (never writes seed).
111
- 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).
147
+
148
+ `inventory` and `reconcile` are offline (no REST, SSH, or password).
149
+ `inventory` turns an SM203520 Settings XML ZIP or `ac.exe export xml` folder into `inventory/`.
150
+
151
+ `reconcile` compares `inventory/` to optional `config/` and writes `findings/` only.
152
+ It never writes seed.
153
+
154
+ Optional `snapshot_map.yaml` (data-repo root, or package defaults) maps DAC tables to catalog entities.
155
+ It normalizes the join: pad-trim, key/field aliases, Account/Sub FK CD resolve, enum label to code.
156
+
112
157
  See [docs/demo-seed.md](docs/demo-seed.md).
158
+
113
159
  `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.
160
+
161
+ Run `acu --help` for the full mental model (workflow, planes, exit codes, command map).
162
+ That is enough for an agent to learn the tool without extra docs.
163
+
164
+ Run `acu <command> --help` (or `-h`) for flags, examples, and prerequisites.
115
165
 
116
166
  ### Dual readers, one writer
117
167
 
@@ -128,38 +178,49 @@ Do not confuse them with each other or with `state`:
128
178
 
129
179
  `inventory/` and `findings/` are engagement outputs: not SEED_DIRS, never loaded by `apply`/`diff`, not scaffolded by `config init`.
130
180
  Binary `.adb` snapshots are rejected (XML only).
181
+
131
182
  See [docs/ac-exe.md](docs/ac-exe.md) for export / SM203520 notes and [docs/demo-seed.md](docs/demo-seed.md) for the extract/state/inventory map.
132
183
 
133
184
  ## The data repo
134
185
 
135
186
  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.
187
+ `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.
188
+
189
+ The Bootstrap endpoint contract is package SoT (`bootstrap_project.xml` inside the CLI — `Bootstrap/1.4.0`).
190
+ `config init` never writes `project.xml`, and data repos must not keep one (a present file hard-errors on bootstrap/publish).
191
+
137
192
  There is no `--flavor`.
138
193
 
139
194
  | Path | What it holds |
140
195
  | ---- | ------------- |
141
- | `config/bootstrap/` | virgin-tenant config: features, company, credit terms, `project.xml` |
196
+ | `config/bootstrap/` | virgin-tenant config: features, company, credit terms (no `project.xml`) |
142
197
  | `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs |
143
198
  | `config/setup/` | one-time actions: financial year, master calendar, open periods |
144
- | `config/master/` | inventory/distribution masters (prefs, warehouse, items, parties) |
199
+ | `config/master/` | inventory/distribution masters: numbering (`05-…`) before prefs, warehouse, items, parties + Role/User (`90-roles` then `91-users`) |
145
200
  | `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
146
201
  | `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
147
202
  | `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
148
203
  | `inventory/` | engagement: offline snapshot tables from `acu inventory` (not seed; not SEED_DIRS) |
149
204
  | `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 |
205
+ | `matrix.yaml` | multi-host pin+where: cells `id`+`erp`+`default_api`+`base_url` (V27); `--cell` selects |
206
+ | `.env` | secrets + optional where override (`ACU_*`); never Default API pin |
152
207
 
153
208
  Legacy data repos may still keep root `bootstrap/`…`master/`; bare `apply`/`diff` prefer `config/` when present and never merge both trees.
154
209
 
155
210
  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.
211
+ Commit `matrix.yaml` with the seeds so every clone knows verified ERP line, Default API half, and REST where per cell.
157
212
 
158
213
  Seed YAML is state: `apply` upserts it, `diff` proves it.
159
214
  `acu extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut).
160
- Packaged `seed_catalog.yaml` is the sole extract registry (entity, endpoint, keys, file, strip/include, filter-split); the demo entity map in [docs/demo-seed.md](docs/demo-seed.md) mirrors those catalog paths.
215
+
216
+ Packaged `seed_catalog.yaml` is the sole extract registry (entity, endpoint, keys, file, strip/include, filter-split).
217
+ The demo entity map in [docs/demo-seed.md](docs/demo-seed.md) mirrors those catalog paths.
218
+
161
219
  Features synthesize to `config/bootstrap/features.yaml`.
162
- Existing files skip unless `--force`; empty live sets skip; row failures continue (exit 1 only if any row failed — drift stays with `diff`).
220
+ Existing files skip unless `--force`; empty live sets skip.
221
+
222
+ Row failures continue (exit 1 only if any row failed).
223
+ Drift stays with `diff`.
163
224
 
164
225
  ```sh
165
226
  acu --tenant DEV extract --out . --force # refresh config/** from live tenant
@@ -174,11 +235,13 @@ Dual-served entities (on both Bootstrap and Default) need an explicit `endpoint:
174
235
  | Value | Resolves to |
175
236
  | ----- | ----------- |
176
237
  | omitted | `Default/<api_version>` for Default-only entities |
177
- | `bootstrap` | active `Bootstrap/<ver>` from `bootstrap/project.xml` or the packaged contract |
238
+ | `bootstrap` | active `Bootstrap/<ver>` from the packaged contract only |
178
239
  | `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) |
240
+ | `Bootstrap/1.4.0` or `Default/25.200.001` | literal pin (ignores the resolved Default version) |
241
+
242
+ `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`).
243
+ `base_url` resolves as `--url`, else `ACU_BASE_URL`, else active cell `base_url`.
180
244
 
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`).
182
245
  Prefer symbolic `default` over a pinned `Default/25.200.001` so the seed tree travels with the dataset pin.
183
246
 
184
247
  ## Installation
@@ -190,25 +253,28 @@ uv tool install acumatica-cli
190
253
  ```
191
254
 
192
255
  `pipx install acumatica-cli` and `pip install acumatica-cli` work too.
193
- For the latest development version straight from the main branch:
256
+
257
+ Or clone and install editable for development:
194
258
 
195
259
  ```sh
196
- uv tool install git+https://github.com/kborovik/acumatica-cli.git
260
+ git clone https://github.com/kborovik/acumatica-cli.git
261
+ cd acumatica-cli
262
+ gmake install # editable install as a global uv tool
197
263
  ```
198
264
 
199
265
  Verify with `acu --version`.
200
266
 
201
267
  ## Configuration
202
268
 
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`).
269
+ Secrets live in one `.env` file (`ACU_*` vars).
270
+ Non-secret **where** and the Default contract pin live in committed `matrix.yaml` (cell `base_url` + `default_api`).
271
+
272
+ Optional `ACU_BASE_URL` overrides cell where for ad-hoc probes.
207
273
 
208
274
  ```sh
209
- ACU_BASE_URL=http://acu-dev1.vm.internal/AcumaticaERP # required: REST root
275
+ # ACU_BASE_URL optional when matrix.yaml cell carries base_url
210
276
  ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
211
- # ACU_SSH omitted → defaults to Administrator@acu-dev1.vm.internal
277
+ # ACU_SSH omitted → defaults to Administrator@ + resolved base_url host
212
278
  # ACU_SSH= # hosted opt-out (blank key)
213
279
  ACU_USER=admin # optional, defaults to admin
214
280
  ACU_PASSWORD=... # required for live commands
@@ -218,34 +284,81 @@ There is no `ACU_API_VERSION` env key (unknown `ACU_*` vars are ignored).
218
284
  Ad-hoc override: `acu --api-version 24.200.001 …` (version half only, never
219
285
  `Default/25.200.001` — a full path would nest as `/entity/Default/Default/...`).
220
286
 
221
- The committed `target.yaml` next to `.env` declares the verified matrix (what, not where) and is the **sole data-repo Default pin**:
287
+ Committed `matrix.yaml` is the **sole data-repo pin+where registry** (1..N cells):
222
288
 
223
289
  ```yaml
224
- erp: "26.101.0225" # claimed product line/build
225
- default_api: "25.200.001" # sources Instance.api_version when --api-version absent
290
+ cells:
291
+ - id: "default"
292
+ erp: "26.101.0225" # claimed product line/build
293
+ default_api: "25.200.001" # sources Instance.api_version when --api-version absent
294
+ base_url: "http://acu-dev1.vm.internal/AcumaticaERP"
226
295
  ```
227
296
 
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).
297
+ `--cell <id>` selects a cell (omit means first cell).
298
+ When present, live commands source `api_version` from cell `default_api` and `base_url` from the cell when flag/env leave them unset.
299
+
300
+ `acu config check` reports `ok matrix (cell=...; api_version from default_api=...; ...)`.
301
+ Missing `matrix.yaml` only warns on `config check` unless you pass `--strict`.
302
+
303
+ `acu check` (lifecycle) **requires** matrix.
304
+
305
+ ### Multi-host matrix (V44)
306
+
307
+ One **trunk seed** in the data repo serves every host.
308
+ Version fan-out is **not** long-running product branches (`acu-25r1`, `acu-26r1`, …).
309
+
310
+ | Piece | Role |
311
+ | ----- | ---- |
312
+ | Trunk seed | Canonical `config/` + `scenario/` (newest supported matrix) |
313
+ | `matrix.yaml` cells | Each host: `id`+`erp`+`default_api`+`base_url`; `--cell` / `acu check --all` |
314
+ | Optional overlays | Surgical seed deltas keyed by Default half (e.g. `overlays/default-24.200.001/`) |
315
+ | OpenAPI | Live `acu schema` dump only (gitignored); never multi-version swagger trees in package or data repo |
316
+
317
+ **Overlays** live under `overlays/default-<default_api>/` (scaffolded by `acu config init`).
318
+ No `--overlay` flag.
319
+
320
+ **Bare compose (pin auto):** when path args are omitted, `acu apply` / `acu diff` append overlay config seed dirs when present.
321
+ `acu run` replaces same-basename scenario files from the pin overlay.
322
+
323
+ Pin = resolved `api_version` (matrix cell `default_api`).
324
+ Explicit path args disable auto-compose.
325
+
326
+ ```sh
327
+ # matrix cell default_api: 24.200.001 → uses overlays/default-24.200.001/
328
+ acu --cell lab25 apply
329
+ acu --cell lab25 run
330
+ acu --cell lab25 diff
331
+
332
+ # explicit path args (no auto) — later path wins same keys
333
+ acu apply config/ overlays/default-24.200.001/
334
+ acu diff config/ overlays/default-24.200.001/
335
+
336
+ # cold lifecycle every cell (SSH + tenant required); tenants left for inspect
337
+ acu check --all --yes --tenant LAB5
338
+ ```
339
+
340
+ Add a future half by creating `overlays/default-<new-half>/` with the
341
+ minimal rewrite; no long-running product branches and no multi-version
342
+ OpenAPI trees (V11/V44).
343
+
344
+ Sibling data-repo retirement of release branches:
345
+ [acumatica-gitops#2](https://github.com/kborovik/acumatica-gitops/issues/2).
233
346
 
234
347
  Worth knowing:
235
348
 
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.
349
+ - The `.env` file is found by walking up from the current directory, so any subdirectory of the data repo works.
350
+ - Without a `.env`, global flags plus the process environment (and matrix cell where) supply the configuration.
351
+ - When `ACU_SSH` is **absent**, acu defaults to `Administrator@` + the resolved base_url hostname.
239
352
  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.
353
+ Only `acu tenant` / `acu check` require a non-empty value post-default.
354
+ - `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
355
  - Redirect it to turn resolved state into a working config: `acu config show > .env`.
243
356
 
244
357
  Verify before touching anything live:
245
358
 
246
359
  ```sh
247
- acu config check # discovery, secrets, target, REST, endpoints, SSH
248
- acu config check --strict # missing target.yaml becomes fail
360
+ acu config check # discovery, secrets, matrix, REST, endpoints, SSH
361
+ acu config check --strict # missing matrix.yaml becomes fail
249
362
  acu apply --dry-run # show what would be written, write nothing
250
363
  ```
251
364
 
@@ -253,6 +366,7 @@ acu apply --dry-run # show what would be written, write nothing
253
366
 
254
367
  Requires **GNU Make at least 3.82** — the Makefile uses `.ONESHELL`.
255
368
  On macOS use Homebrew's `gmake` (`brew install make`); `/usr/bin/make` is 3.81 and fails the guard.
369
+
256
370
  Elsewhere plain `make` is fine when it is GNU Make.
257
371
 
258
372
  ```sh
@@ -264,17 +378,30 @@ gmake check # offline gate: ruff, basedpyright strict, pytest
264
378
 
265
379
  The default test suite is fully offline.
266
380
  REST is faked with `httpx.MockTransport`, SSH with a monkeypatched `subprocess.run` — no live instance is needed.
381
+
267
382
  `gmake check` must pass before every commit.
268
383
  GitHub Actions runs the same gate on every push and pull request to `main`.
269
384
 
270
385
  ### Release
271
386
 
387
+ Human release notes live in root [`CHANGELOG.md`](CHANGELOG.md) (Keep a Changelog).
388
+ During development, append user-facing work under `## Unreleased` in `### Added` / `### Changed` / `### Fixed` as appropriate.
389
+
390
+ Empty Unreleased (no bullets) hard-fails the release — nothing to ship.
391
+
272
392
  ```sh
273
393
  gmake release patch # or minor | major
274
394
  ```
275
395
 
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.
396
+ `gmake release` is the sole release path (never local `gh release create`):
397
+
398
+ 1. `gmake check` (ruff, basedpyright, offline pytest)
399
+ 2. Fail if `## Unreleased` has no bullets
400
+ 3. Bump `pyproject.toml` version (`major` | `minor` | `patch`)
401
+ 4. Promote Unreleased body to `## [vX.Y.Z] - YYYY-MM-DD`, leave an empty `## Unreleased`
402
+ 5. Commit `CHANGELOG.md` + `pyproject.toml` (+ lock if bumped) together, tag `vX.Y.Z`, push
403
+
404
+ 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
405
 
279
406
  ### Live end-to-end tier
280
407
 
@@ -285,6 +412,7 @@ Configuration is one file: a decrypted `.env` at the repo root names the instanc
285
412
 
286
413
  The tier is self-contained.
287
414
  Each run scaffolds a synthetic single-org company from the packaged `acu config init` templates into a temporary directory, copies the real `.env` into it, and runs the installed `acu` binary from there — no data repo, no pre-existing fixtures on the instance.
415
+
288
416
  Scratch tenants (`E2E`, `E2EA`, `E2EB`, `E2ESCEN`) are created on the way in and always deleted on the way out, so nothing persists.
289
417
  The packaged full `config init` seed (under `config/`) is the only scaffold.
290
418
 
@@ -298,6 +426,7 @@ gmake e2e FILE=test_scenario_lifecycle # scenario + state focus
298
426
 
299
427
  This project is licensed under the PolyForm Noncommercial License 1.0.0.
300
428
  Noncommercial use is free under that license.
429
+
301
430
  Commercial use requires a separate license — contact [lab5.ca](https://lab5.ca).
302
431
 
303
432
  See [LICENSE](LICENSE) and [NOTICE](NOTICE).