acumatica-cli 0.33.1__tar.gz → 0.34.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 (77) hide show
  1. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/PKG-INFO +27 -13
  2. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/README.md +26 -12
  3. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/pyproject.toml +1 -1
  4. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/pyproject.toml.orig +1 -1
  5. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/bootstrap.py +4 -4
  6. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/bootstrap_plugin.cs +1 -1
  7. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/bootstrap_project.xml +22 -9
  8. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/cli.py +3 -3
  9. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/client.py +5 -5
  10. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/extract.py +5 -2
  11. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/firstlogin.py +2 -2
  12. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/inventory.py +1 -1
  13. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/seed.py +7 -6
  14. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/seed_catalog.yaml +6 -5
  15. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/05-numbering-sequences.yaml +13 -0
  16. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/tenant.py +9 -9
  17. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/__init__.py +0 -0
  18. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/config.py +0 -0
  19. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/models.py +0 -0
  20. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/output.py +0 -0
  21. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/reconcile.py +0 -0
  22. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/run.py +0 -0
  23. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/snapshot_map.yaml +0 -0
  24. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/state.py +0 -0
  25. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/README.md +0 -0
  26. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/baseline/10-subaccounts.yaml +0 -0
  27. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/baseline/20-accounts.yaml +0 -0
  28. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/baseline/40-ledger.yaml +0 -0
  29. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/baseline/50-gl-preferences.yaml +0 -0
  30. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/baseline/60-ledger-company.yaml +0 -0
  31. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/baseline/90-uoms.yaml +0 -0
  32. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/baseline/91-company-packaging.yaml +0 -0
  33. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/bootstrap/company.yaml +0 -0
  34. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/bootstrap/credit-terms.yaml +0 -0
  35. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/bootstrap/features.yaml +0 -0
  36. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/bootstrap/segmented-key.yaml +0 -0
  37. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/10-reason-codes.yaml +0 -0
  38. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/20-in-preferences.yaml +0 -0
  39. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/30-availability-rules.yaml +0 -0
  40. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/40-posting-classes.yaml +0 -0
  41. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/50-warehouse.yaml +0 -0
  42. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/51-warehouse-locations.yaml +0 -0
  43. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/52-warehouse-defaults.yaml +0 -0
  44. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/53-tax-categories.yaml +0 -0
  45. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/54-item-classes.yaml +0 -0
  46. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/56-so-preferences.yaml +0 -0
  47. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/57-po-preferences.yaml +0 -0
  48. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/58-order-types.yaml +0 -0
  49. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +0 -0
  50. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +0 -0
  51. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/62-ca-preferences.yaml +0 -0
  52. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/63-cash-account.yaml +0 -0
  53. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/64-payment-methods.yaml +0 -0
  54. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/65-statement-cycles.yaml +0 -0
  55. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/70-vendor-classes.yaml +0 -0
  56. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/71-customer-classes.yaml +0 -0
  57. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/75-vendors.yaml +0 -0
  58. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/76-customers.yaml +0 -0
  59. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/80-stock-items-parts.yaml +0 -0
  60. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/82-stock-items-kits.yaml +0 -0
  61. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/85-kit-specifications.yaml +0 -0
  62. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/90-roles.yaml +0 -0
  63. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/91-users.yaml +0 -0
  64. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/master/92-role-users.yaml +0 -0
  65. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/setup/10-financial-year.yaml +0 -0
  66. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/setup/20-master-calendar.yaml +0 -0
  67. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/setup/30-open-periods.yaml +0 -0
  68. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/config/views/10-trial-balance.yaml +0 -0
  69. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/env +0 -0
  70. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/gitignore +0 -0
  71. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/overlays/README.md +0 -0
  72. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/overlays/default-24.200.001/README.md +0 -0
  73. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/overlays/default-24.200.001/scenario/30-build.yaml +0 -0
  74. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/scenario/10-seed-capital.yaml +0 -0
  75. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/scenario/20-buy.yaml +0 -0
  76. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/scenario/30-build.yaml +0 -0
  77. {acumatica_cli-0.33.1 → acumatica_cli-0.34.0}/src/acumatica_cli/templates/scenario/40-sell.yaml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: acumatica-cli
3
- Version: 0.33.1
3
+ Version: 0.34.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>
@@ -79,7 +79,6 @@ acu --tenant DEV state # capture state/ trial-balance
79
79
  ```
80
80
 
81
81
  Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
82
- 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.
83
82
 
84
83
  **Hosted Acumatica (no SSH):** the tenant already exists; set a blank `ACU_SSH=` in `.env`.
85
84
  The scaffold omits the key — without it, acu defaults to `Administrator@<ACU_BASE_URL host>` for SSH boxes.
@@ -140,7 +139,7 @@ When you omit FILES:
140
139
  - `state` defaults to `config/views/`; writes go to `state/` (`--out`).
141
140
 
142
141
  Scenario YAML may use `${current_period}` (host-local `MMyyyy`) on steps, expect params, and `once.present` params.
143
- `config/views` and `state` keep Period pinned — see [docs/demo-seed.md](docs/demo-seed.md#period-token-current_period-vs-pinned-views).
142
+ `config/views` and `state` keep Period pinned.
144
143
 
145
144
  `survey extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
146
145
 
@@ -153,8 +152,6 @@ It never writes seed.
153
152
  Optional `snapshot_map.yaml` (data-repo root, or package defaults) maps DAC tables to catalog entities.
154
153
  It normalizes the join: pad-trim, key/field aliases, Account/Sub FK CD resolve, enum label to code.
155
154
 
156
- See [docs/demo-seed.md](docs/demo-seed.md).
157
-
158
155
  `acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
159
156
 
160
157
  Run `acu --help` for the full mental model (workflow, planes, exit codes, command map).
@@ -178,8 +175,6 @@ Do not confuse them with each other or with `state`:
178
175
  `inventory/` and `findings/` are engagement outputs: not SEED_DIRS, never loaded by `apply`/`diff`, not scaffolded by `config init`.
179
176
  Binary `.adb` snapshots are rejected (XML only).
180
177
 
181
- 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.
182
-
183
178
  ## The data repo
184
179
 
185
180
  Your configuration lives in its own git repo.
@@ -212,7 +207,6 @@ Seed YAML is state: `apply` upserts it, `diff` proves it.
212
207
  `acu survey extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut).
213
208
 
214
209
  Packaged `seed_catalog.yaml` is the sole extract registry (entity, endpoint, keys, file, strip/include, filter-split).
215
- The demo entity map in [docs/demo-seed.md](docs/demo-seed.md) mirrors those catalog paths.
216
210
 
217
211
  Features synthesize to `config/bootstrap/features.yaml`.
218
212
  Existing files skip unless `--force`; empty live sets skip.
@@ -268,7 +262,7 @@ Secrets, REST where, and the Default contract pin live in one `.env` file (`ACU_
268
262
  Leftover `matrix.yaml` is ignored and never loaded.
269
263
 
270
264
  ```sh
271
- ACU_BASE_URL=http://acu-dev1.vm.internal/AcumaticaERP # REST where (required)
265
+ ACU_BASE_URL=http://erp.example.com/AcumaticaERP # REST where (required)
272
266
  ACU_API_VERSION=25.200.001 # Default contract half
273
267
  ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
274
268
  # ACU_SSH omitted → defaults to Administrator@ + resolved base_url host
@@ -391,16 +385,36 @@ GitHub Actions on tag `v*` re-runs CI, builds sdist+wheel, publishes to PyPI via
391
385
  Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `ACU_TENANT`, `ACU_PASSWORD` (and optional `ACU_SSH`; omitted defaults to `Administrator@` + base-url host).
392
386
  `gmake e2e` refuses to start without it.
393
387
 
394
- The tier is self-contained.
388
+ The tier is three pipeline files: provision (apply/diff), scenario (run/state), and extract round-trip.
389
+ Per-bug contract probes fold onto those tenants.
390
+
395
391
  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.
396
392
 
397
393
  Scratch tenants (`E2E`, `E2EA`, `E2EB`, `E2ESCEN`) are created on the way in and always deleted on the way out, so nothing persists.
398
394
  The packaged full `config init` seed (under `config/`) is the only scaffold.
399
395
 
400
396
  ```sh
401
- gmake e2e # whole tier, about 20 minutes
402
- gmake e2e FILE=test_provision_lifecycle # apply/diff focus
403
- gmake e2e FILE=test_scenario_lifecycle # scenario + state focus
397
+ gmake e2e # whole tier, three files
398
+ gmake e2e FILE=test_provision_lifecycle # apply/diff + folded probes
399
+ gmake e2e FILE=test_scenario_lifecycle # scenario + state + kit alloc
400
+ gmake e2e FILE=test_extract_roundtrip # extract inverse
401
+ ```
402
+
403
+ ### CLI test-seed soak
404
+
405
+ This checkout carries a CLI test seed at repo-root `config/` and `scenario/` (no `config/qms/`).
406
+ Soak the live CLI tenant from here with `--tenant ACUCLI`.
407
+
408
+ Never use tenant `CNBN`. Never `cd` the sibling GitOps repo for CLI soak.
409
+
410
+ `gmake e2e` still scaffolds from packaged `config init` templates into a tmp dir.
411
+ That path is not the soak.
412
+
413
+ ```sh
414
+ uv run acu --tenant ACUCLI apply
415
+ uv run acu --tenant ACUCLI run
416
+ uv run acu --tenant ACUCLI diff
417
+ uv run acu --tenant ACUCLI state
404
418
  ```
405
419
 
406
420
  ## License
@@ -61,7 +61,6 @@ acu --tenant DEV state # capture state/ trial-balance
61
61
  ```
62
62
 
63
63
  Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
64
- 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.
65
64
 
66
65
  **Hosted Acumatica (no SSH):** the tenant already exists; set a blank `ACU_SSH=` in `.env`.
67
66
  The scaffold omits the key — without it, acu defaults to `Administrator@<ACU_BASE_URL host>` for SSH boxes.
@@ -122,7 +121,7 @@ When you omit FILES:
122
121
  - `state` defaults to `config/views/`; writes go to `state/` (`--out`).
123
122
 
124
123
  Scenario YAML may use `${current_period}` (host-local `MMyyyy`) on steps, expect params, and `once.present` params.
125
- `config/views` and `state` keep Period pinned — see [docs/demo-seed.md](docs/demo-seed.md#period-token-current_period-vs-pinned-views).
124
+ `config/views` and `state` keep Period pinned.
126
125
 
127
126
  `survey extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
128
127
 
@@ -135,8 +134,6 @@ It never writes seed.
135
134
  Optional `snapshot_map.yaml` (data-repo root, or package defaults) maps DAC tables to catalog entities.
136
135
  It normalizes the join: pad-trim, key/field aliases, Account/Sub FK CD resolve, enum label to code.
137
136
 
138
- See [docs/demo-seed.md](docs/demo-seed.md).
139
-
140
137
  `acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
141
138
 
142
139
  Run `acu --help` for the full mental model (workflow, planes, exit codes, command map).
@@ -160,8 +157,6 @@ Do not confuse them with each other or with `state`:
160
157
  `inventory/` and `findings/` are engagement outputs: not SEED_DIRS, never loaded by `apply`/`diff`, not scaffolded by `config init`.
161
158
  Binary `.adb` snapshots are rejected (XML only).
162
159
 
163
- 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.
164
-
165
160
  ## The data repo
166
161
 
167
162
  Your configuration lives in its own git repo.
@@ -194,7 +189,6 @@ Seed YAML is state: `apply` upserts it, `diff` proves it.
194
189
  `acu survey extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut).
195
190
 
196
191
  Packaged `seed_catalog.yaml` is the sole extract registry (entity, endpoint, keys, file, strip/include, filter-split).
197
- The demo entity map in [docs/demo-seed.md](docs/demo-seed.md) mirrors those catalog paths.
198
192
 
199
193
  Features synthesize to `config/bootstrap/features.yaml`.
200
194
  Existing files skip unless `--force`; empty live sets skip.
@@ -250,7 +244,7 @@ Secrets, REST where, and the Default contract pin live in one `.env` file (`ACU_
250
244
  Leftover `matrix.yaml` is ignored and never loaded.
251
245
 
252
246
  ```sh
253
- ACU_BASE_URL=http://acu-dev1.vm.internal/AcumaticaERP # REST where (required)
247
+ ACU_BASE_URL=http://erp.example.com/AcumaticaERP # REST where (required)
254
248
  ACU_API_VERSION=25.200.001 # Default contract half
255
249
  ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
256
250
  # ACU_SSH omitted → defaults to Administrator@ + resolved base_url host
@@ -373,16 +367,36 @@ GitHub Actions on tag `v*` re-runs CI, builds sdist+wheel, publishes to PyPI via
373
367
  Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `ACU_TENANT`, `ACU_PASSWORD` (and optional `ACU_SSH`; omitted defaults to `Administrator@` + base-url host).
374
368
  `gmake e2e` refuses to start without it.
375
369
 
376
- The tier is self-contained.
370
+ The tier is three pipeline files: provision (apply/diff), scenario (run/state), and extract round-trip.
371
+ Per-bug contract probes fold onto those tenants.
372
+
377
373
  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.
378
374
 
379
375
  Scratch tenants (`E2E`, `E2EA`, `E2EB`, `E2ESCEN`) are created on the way in and always deleted on the way out, so nothing persists.
380
376
  The packaged full `config init` seed (under `config/`) is the only scaffold.
381
377
 
382
378
  ```sh
383
- gmake e2e # whole tier, about 20 minutes
384
- gmake e2e FILE=test_provision_lifecycle # apply/diff focus
385
- gmake e2e FILE=test_scenario_lifecycle # scenario + state focus
379
+ gmake e2e # whole tier, three files
380
+ gmake e2e FILE=test_provision_lifecycle # apply/diff + folded probes
381
+ gmake e2e FILE=test_scenario_lifecycle # scenario + state + kit alloc
382
+ gmake e2e FILE=test_extract_roundtrip # extract inverse
383
+ ```
384
+
385
+ ### CLI test-seed soak
386
+
387
+ This checkout carries a CLI test seed at repo-root `config/` and `scenario/` (no `config/qms/`).
388
+ Soak the live CLI tenant from here with `--tenant ACUCLI`.
389
+
390
+ Never use tenant `CNBN`. Never `cd` the sibling GitOps repo for CLI soak.
391
+
392
+ `gmake e2e` still scaffolds from packaged `config init` templates into a tmp dir.
393
+ That path is not the soak.
394
+
395
+ ```sh
396
+ uv run acu --tenant ACUCLI apply
397
+ uv run acu --tenant ACUCLI run
398
+ uv run acu --tenant ACUCLI diff
399
+ uv run acu --tenant ACUCLI state
386
400
  ```
387
401
 
388
402
  ## License
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acumatica-cli"
3
- version = "0.33.1"
3
+ version = "0.34.0"
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"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acumatica-cli"
3
- version = "0.33.1"
3
+ version = "0.34.0"
4
4
  description = "Acumatica ERP Config-as-Code: tenant provisioning, baseline config, and reference data"
5
5
  authors = [{ name = "Konstantin Borovik", email = "kb@lab5.ca" }]
6
6
  license = "PolyForm-Noncommercial-1.0.0"
@@ -1,15 +1,15 @@
1
1
  """Bootstrap customization package: build the zip, publish via /CustomizationApi.
2
2
 
3
3
  An unconfigured tenant cannot be configured through the Default endpoint
4
- (features are off, no company/branch exists, credit terms have no entity —
5
- docs/rest-api.md). The CustomizationApi is the one door that works on a
4
+ (features are off, no company/branch exists, credit terms have no entity).
5
+ The CustomizationApi is the one door that works on a
6
6
  virgin tenant, so bootstrap = publish a package whose CustomizationPlugin
7
7
  (`bootstrap_plugin.cs`) enables features on publish — the contract API
8
8
  cannot write CS100000 at all (T3 verdict) — and whose Bootstrap contract
9
9
  endpoint exposes the seeding surface (serialization verified T12).
10
10
 
11
11
  Bootstrap endpoint contract is package SoT (V2/V21/T178): always the
12
- packaged full company ``bootstrap_project.xml`` (``Bootstrap/1.10.0``).
12
+ packaged full company ``bootstrap_project.xml`` (``Bootstrap/1.11.0``).
13
13
  Data-repo ``config/bootstrap/project.xml`` or ``bootstrap/project.xml`` is
14
14
  not a seed — present → hard error naming package SoT (no dual contract line).
15
15
 
@@ -68,7 +68,7 @@ def _bootstrap_file(root: Path, name: str) -> Path | None:
68
68
 
69
69
 
70
70
  def packaged_contract_xml() -> bytes:
71
- """The CLI-shipped full company Bootstrap contract (``Bootstrap/1.10.0``)."""
71
+ """The CLI-shipped full company Bootstrap contract (``Bootstrap/1.11.0``)."""
72
72
  return (resources.files("acumatica_cli") / "bootstrap_project.xml").read_bytes()
73
73
 
74
74
 
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Runs in-process on customization publish - the one write path to
4
4
  // FeaturesSet that works: the contract API cannot persist features no
5
- // matter what endpoint fronts CS100000 (T3 verdict, docs/rest-api.md).
5
+ // matter what endpoint fronts CS100000 (T3 verdict).
6
6
  //
7
7
  // Deliberately writes through PXDatabase, not FeaturesMaint: the graph
8
8
  // save collides with the publish pipeline's concurrent plugin invocations
@@ -13,11 +13,14 @@
13
13
  and drops Selected (AllowedRoles was EPLoginTypeAllowsRole / EP202500);
14
14
  T228 bumps 1.8.0 -> 1.9.0 for Role.Users -> UsersByRole (SM201005
15
15
  Users tab; mapped-detail PUT is a no-op — B33) and 1.9.0 -> 1.10.0
16
- for Role AssignUser action (PXDatabase UsersInRoles persist).
16
+ for Role AssignUser action (PXDatabase UsersInRoles persist);
17
+ T251 bumps 1.10.0 -> 1.11.0 for NumberingSequence Header NewSymbol
18
+ (CS201010 insert requires New Number Symbol or Manual Numbering;
19
+ gh #44 / V58).
17
20
 
18
- package_zip() (bootstrap.py) prefers data-repo bootstrap/project.xml when
19
- present; otherwise this packaged template is the fallback so config init,
20
- offline tests, and package-only installs still bootstrap the full surface.
21
+ package_zip() always loads this packaged contract. Data-repo
22
+ config/bootstrap/project.xml or bootstrap/project.xml is forbidden (V21);
23
+ present -> hard error naming package SoT.
21
24
 
22
25
  package_zip() injects a
23
26
  <Graph ClassName="AcuBootstrapPlugin" FileType="NewFile" Source="..."/>
@@ -64,16 +67,22 @@
64
67
  T150 NumberingSequence (gh #25) - Default endpoint has no numbering
65
68
  surface; CS201010 Numbering Sequences (NumberingMaint — NOT the
66
69
  misnamed NumberingSequenceMaint):
67
- - PrimaryView Header (DAC Numbering): key NumberingID + Descr.
70
+ - PrimaryView Header (DAC Numbering): key NumberingID + Descr +
71
+ NewSymbol (T251/V58; CS201010 "New Number Symbol"). UserNumbering
72
+ is the OR alternative (manual numbering), not a required pair.
73
+ Mapped GET returns NewSymbol (seed; GET-omit is mapping-miss drift).
68
74
  - Detail view Sequence (DAC NumberingSequence): bounds StartNbr,
69
75
  EndNbr, WarnNbr, NbrStep, StartDate. LastNbr is runtime counter —
70
76
  omitted from contract (V40/T152 strip/ignore if ever present).
71
- - Flat entity (Currency multi-view idiom): NumberingID/Descr on Header,
72
- bounds on Sequence. Single sequence row per NumberingID is the
73
- demo/LAB5 shape; multi-segment / branch-scoped rows are out of v1.
77
+ - Flat entity (Currency multi-view idiom): NumberingID/Descr/NewSymbol
78
+ on Header, bounds on Sequence. Single sequence row per NumberingID
79
+ is the demo/LAB5 shape; multi-segment / branch-scoped rows are out
80
+ of v1.
74
81
  T159: view names were wrong in 1.2.0/1.3.0 (Sequence/SequenceDetail);
75
82
  live aspx + NumberingMaint reflection = Header/Sequence. Bump 1.3.0
76
83
  -> 1.4.0 (V21). Wrong maps made every PUT 422 on NewSymbol mask.
84
+ T251: map NewSymbol to Header (not Sequence). Insert of a new
85
+ NumberingID without it 422s (B38).
77
86
 
78
87
  T204 SegmentedKey (gh #30 / V50) - Default endpoint has no CS202000
79
88
  surface; DimensionMaint Segmented Keys:
@@ -121,7 +130,7 @@
121
130
  -->
122
131
  <Customization level="" description="acu bootstrap: CustomizationPlugin enables features on publish; Bootstrap endpoint exposes full company surface (company + credit terms + financial currency + GL chain + module preferences + vendor class + statement cycle + warehouse + order type + cash account + role + user + membership + numbering sequence + segmented key)" product-version="26.101">
123
132
  <EntityEndpoint>
124
- <Endpoint xmlns="http://www.acumatica.com/entity/maintenance/5.31" name="Bootstrap" version="1.10.0" systemContractVersion="4">
133
+ <Endpoint xmlns="http://www.acumatica.com/entity/maintenance/5.31" name="Bootstrap" version="1.11.0" systemContractVersion="4">
125
134
  <TopLevelEntity name="Company" screen="CS101500">
126
135
  <Fields>
127
136
  <Field name="AcctCD" type="StringValue" />
@@ -1120,6 +1129,7 @@
1120
1129
  <Fields>
1121
1130
  <Field name="NumberingID" type="StringValue" />
1122
1131
  <Field name="Descr" type="StringValue" />
1132
+ <Field name="NewSymbol" type="StringValue" />
1123
1133
  <Field name="StartNbr" type="StringValue" />
1124
1134
  <Field name="EndNbr" type="StringValue" />
1125
1135
  <Field name="WarnNbr" type="StringValue" />
@@ -1133,6 +1143,9 @@
1133
1143
  <Mapping field="Descr">
1134
1144
  <To object="Header" field="Descr" />
1135
1145
  </Mapping>
1146
+ <Mapping field="NewSymbol">
1147
+ <To object="Header" field="NewSymbol" />
1148
+ </Mapping>
1136
1149
  <Mapping field="StartNbr">
1137
1150
  <To object="Sequence" field="StartNbr" />
1138
1151
  </Mapping>
@@ -76,7 +76,7 @@ DEFAULT PATHS (when FILES omitted)
76
76
  survey extract -> always writes config/{bootstrap,baseline,setup,master}/
77
77
 
78
78
  Run `acu <command> --help` for flags, examples, and prerequisites.
79
- See package README and docs/demo-seed.md for seed YAML shape.
79
+ See package README for seed YAML shape.
80
80
  """
81
81
 
82
82
  import functools
@@ -382,7 +382,7 @@ def tenant_list(inst: Instance) -> None:
382
382
  @click.option(
383
383
  "--type",
384
384
  "company_type",
385
- # the V12-verified dataset folders on the box (docs/ac-exe.md); System
385
+ # the V12-verified dataset folders on the box; System
386
386
  # is the system-tenant dataset, deliberately not offered
387
387
  type=click.Choice(["SalesDemo", "T100", "U100"]),
388
388
  default=None,
@@ -466,7 +466,7 @@ def tenant_create(
466
466
  output.data(raw.splitlines()[-1] if raw.strip() else "created")
467
467
  # ac.exe only sets CompanyKey from LoginName; CompanyCD stays auto-generated
468
468
  # (Company2/Company3/…). Align CD → login so list shows matching columns.
469
- # Idempotent on re-run / exists-skip (docs/ac-exe.md).
469
+ # Idempotent on re-run / exists-skip.
470
470
  with output.step(f"aligning CompanyCD to login ({login_name})"):
471
471
  if mgr.set_company_cd(company_id, login_name):
472
472
  output.data(f"CompanyCD set to {login_name}")
@@ -1,4 +1,4 @@
1
- """Contract-based REST API session (see docs/rest-api.md for verified quirks)."""
1
+ """Contract-based REST API session."""
2
2
 
3
3
  import base64
4
4
  import html
@@ -162,7 +162,7 @@ OPTIMIZATION_500 = "Optimization cannot be performed"
162
162
  # tenant answers PXSetupNotEnteredException on most entities until bootstrap
163
163
  # lands, and the exceptionMessage names the missing form — "The required
164
164
  # configuration data is not entered on the <Form> form." (verified vs
165
- # 26.101.0225, docs/rest-api.md).
165
+ # 26.101.0225).
166
166
  SETUP_NOT_ENTERED_500 = "configuration data is not entered"
167
167
 
168
168
 
@@ -188,7 +188,7 @@ class AcumaticaClient:
188
188
  self._refreshed_after_company = False
189
189
 
190
190
  def __enter__(self) -> AcumaticaClient:
191
- # tenant guard (V5, docs/rest-api.md): an omitted or empty tenant is
191
+ # tenant guard (V5): an omitted or empty tenant is
192
192
  # the one login the server still routes silently — to the default
193
193
  # tenant. Defense-in-depth vs wrong-tenant writes: every data-plane
194
194
  # session must name its tenant, so refuse before any HTTP happens.
@@ -219,7 +219,7 @@ class AcumaticaClient:
219
219
 
220
220
  Login accepting the name proves nothing - a stale tenant map
221
221
  reroutes named logins to the default tenant, and a single-tenant
222
- instance accepts ANY name (both verified live, docs/rest-api.md).
222
+ instance accepts ANY name (both verified live).
223
223
  """
224
224
  creds: dict[str, str] = {
225
225
  "name": self.instance.user,
@@ -261,7 +261,7 @@ class AcumaticaClient:
261
261
  def _landed_tenant(self) -> str:
262
262
  """The tenant this session actually landed on (login name).
263
263
 
264
- Verified vs 26.101.0225 (docs/rest-api.md): the contract API exposes
264
+ Verified vs 26.101.0225: the contract API exposes
265
265
  nothing tenant-identifying, but an authenticated GET of the sign-in
266
266
  page renders a hidden ``txtSingleCompany`` input whose value is the
267
267
  session tenant's login name in every observed state - multi-tenant,
@@ -465,8 +465,11 @@ def _synth_open_periods(client: AcumaticaClient) -> dict[str, Any] | None:
465
465
  years = _years(live)
466
466
  # OrganizationID = the extracted Company's AcctCD: the reference
467
467
  # resolves inside the emitted set (V22 - config/bootstrap/company.yaml
468
- # creates the organization the action names)
469
- companies = client.get_list("Company", endpoint=ep)
468
+ # creates the organization the action names).
469
+ # Company list GET 500s once DecPlQty/WeightUOM/VolumeUOM map
470
+ # commonsetup (V52; BQL-delegate view, B9). $select keeps AcctCD
471
+ # only so the synth does not pull those fields.
472
+ companies = client.get_list("Company", params={"$select": "AcctCD"}, endpoint=ep)
470
473
  if not companies:
471
474
  raise RuntimeError("open-periods: no Company on tenant")
472
475
  org = sorted(str(unwrap(c)["AcctCD"]) for c in companies)[0]
@@ -1,8 +1,8 @@
1
1
  """First-login admin password initialization via the Login.aspx screen flow.
2
2
 
3
3
  A freshly created tenant seeds ``admin``/``setup`` with a
4
- must-change-on-first-login flag the contract REST API cannot clear (verified —
5
- see docs/rest-api.md). The sign-in screen's WebForms flow can, with plain
4
+ must-change-on-first-login flag the contract REST API cannot clear (verified
5
+ live). The sign-in screen's WebForms flow can, with plain
6
6
  HTTP: GET the page for the hidden fields, POST the seed credentials, then
7
7
  POST again with the new password into the change view the server renders.
8
8
  """
@@ -13,7 +13,7 @@ Accepted artifacts (V37):
13
13
  Rejected: binary ``.adb`` (and ZIPs that only carry ``.adb``) — named
14
14
  error, fail-closed.
15
15
 
16
- Table XML shape (verified ac-exe export / dataset format, docs/ac-exe.md):
16
+ Table XML shape (verified ac-exe export / dataset format):
17
17
 
18
18
  <data>
19
19
  <table name="Account">
@@ -83,17 +83,18 @@ _BRANCH_EMPTY = "'Branch' cannot be empty"
83
83
  # YAML; extract always strips; diff never compares (GET returns hashes
84
84
  # or nothing — never a re-seedable value).
85
85
  PASSWORD_FIELDS = frozenset({"Password", "b64__Password"})
86
- # NumberingSequence runtime counters (V40/gh #25): bounds-only seed;
87
- # LastNbr (+ advanced counter if ever exposed) = issued progress, not
88
- # desired config. Extract hard-strips; diff never compares; apply never
89
- # PUTs them (re-apply must not reset live counters).
86
+ # NumberingSequence runtime counters (V40/gh #25): LastNbr (+ advanced
87
+ # counter if ever exposed) = issued progress, not desired config. Extract
88
+ # hard-strips; diff never compares; apply never PUTs them (re-apply must
89
+ # not reset live counters).
90
90
  NUMBERING_RUNTIME_FIELDS = frozenset({"LastNbr"})
91
91
  # Fields omitted from source↔live compare (V39 write-only + V40 runtime).
92
- # Field-name deny at every nesting level. Do not add bare ``Value`` here —
93
- # that would hide real drift on unrelated fields (V56).
92
+ # Field-name deny at every nesting level.
94
93
  _DIFF_IGNORE_FIELDS = PASSWORD_FIELDS | NUMBERING_RUNTIME_FIELDS
95
94
  # Path-qualified write-only GET-omit (V56): (entity, detail, field).
96
95
  # Diff ignores; extract strips; apply still PUTs when present in YAML.
96
+ # Do not add bare ``Value`` to _DIFF_IGNORE_FIELDS — that would hide
97
+ # real drift on unrelated fields.
97
98
  _DIFF_IGNORE_PATHS = frozenset({("LotSerialClass", "Segments", "Value")})
98
99
  # Whole-detail skip (V57/B37): mapped GET Role.Users / User.Roles may be
99
100
  # empty while UsersInRoles has session-company rows. Extra live members
@@ -6,7 +6,7 @@
6
6
  # entity contract entity name (the GET source and the emitted `entity:`)
7
7
  # endpoint omitted = the instance Default endpoint; the symbolic name
8
8
  # `bootstrap` stays symbolic in emitted files and resolves to
9
- # the packaged full company Bootstrap/1.10.0 at HTTP/load time
9
+ # the packaged full company Bootstrap/1.11.0 at HTTP/load time
10
10
  # (package SoT - V20/V21/T178; data-repo project.xml forbidden)
11
11
  # keys key fields: the emitted `key:`, the record sort order, and
12
12
  # the B9 fallback's $select projection; the tuple must
@@ -181,10 +181,10 @@ entities:
181
181
  # include field-partition (same live warehouse row, three apply phases).
182
182
  # Multi-file StockItem uses OData filter-split on ItemClass.
183
183
  #
184
- # NumberingSequence (T151/gh #25): V22 prefix 05- sorts before every prefs
185
- # file that may reference *NumberingID (IN/SO/PO/AR/AP/CA/GL setup). Bootstrap
186
- # only — Default has no numbering surface. Bounds-only include (no LastNbr;
187
- # runtime counter is V40/T152).
184
+ # NumberingSequence (T151/gh #25, T252/gh #44): V22 prefix 05- sorts before
185
+ # every prefs file that may reference *NumberingID (IN/SO/PO/AR/AP/CA/GL
186
+ # setup). Bootstrap only — Default has no numbering surface. Include bounds
187
+ # + NewSymbol (insert-required, V58); no LastNbr (runtime counter is V40).
188
188
 
189
189
  - entity: NumberingSequence
190
190
  endpoint: bootstrap
@@ -192,6 +192,7 @@ entities:
192
192
  file: config/master/05-numbering-sequences.yaml
193
193
  include:
194
194
  - Descr
195
+ - NewSymbol
195
196
  - StartNbr
196
197
  - EndNbr
197
198
  - WarnNbr
@@ -3,78 +3,91 @@ key: NumberingID
3
3
  endpoint: bootstrap
4
4
  records:
5
5
  - NumberingID: APBILL
6
+ NewSymbol: '<NEW>'
6
7
  StartNbr: '000000'
7
8
  EndNbr: '999999'
8
9
  WarnNbr: '999990'
9
10
  NbrStep: 1
10
11
  StartDate: '1900-01-01'
11
12
  - NumberingID: APPAYMENT
13
+ NewSymbol: '<NEW>'
12
14
  StartNbr: '000000'
13
15
  EndNbr: '999999'
14
16
  WarnNbr: '999990'
15
17
  NbrStep: 1
16
18
  StartDate: '1900-01-01'
17
19
  - NumberingID: ARINVOICE
20
+ NewSymbol: '<NEW>'
18
21
  StartNbr: '000000'
19
22
  EndNbr: '999999'
20
23
  WarnNbr: '999990'
21
24
  NbrStep: 1
22
25
  StartDate: '1900-01-01'
23
26
  - NumberingID: ARPAYMENT
27
+ NewSymbol: '<NEW>'
24
28
  StartNbr: '000000'
25
29
  EndNbr: '999999'
26
30
  WarnNbr: '999990'
27
31
  NbrStep: 1
28
32
  StartDate: '1900-01-01'
29
33
  - NumberingID: BATCH
34
+ NewSymbol: '<NEW>'
30
35
  StartNbr: '000000'
31
36
  EndNbr: '999999'
32
37
  WarnNbr: '999990'
33
38
  NbrStep: 1
34
39
  StartDate: '1900-01-01'
35
40
  - NumberingID: INADJUST
41
+ NewSymbol: '<NEW>'
36
42
  StartNbr: '000000'
37
43
  EndNbr: '999999'
38
44
  WarnNbr: '999990'
39
45
  NbrStep: 1
40
46
  StartDate: '1900-01-01'
41
47
  - NumberingID: INISSUE
48
+ NewSymbol: '<NEW>'
42
49
  StartNbr: '000000'
43
50
  EndNbr: '999999'
44
51
  WarnNbr: '999990'
45
52
  NbrStep: 1
46
53
  StartDate: '1900-01-01'
47
54
  - NumberingID: INKITASSY
55
+ NewSymbol: '<NEW>'
48
56
  StartNbr: '000000'
49
57
  EndNbr: '999999'
50
58
  WarnNbr: '999990'
51
59
  NbrStep: 1
52
60
  StartDate: '1900-01-01'
53
61
  - NumberingID: INRECEIPT
62
+ NewSymbol: '<NEW>'
54
63
  StartNbr: '000000'
55
64
  EndNbr: '999999'
56
65
  WarnNbr: '999990'
57
66
  NbrStep: 1
58
67
  StartDate: '1900-01-01'
59
68
  - NumberingID: POORDER
69
+ NewSymbol: '<NEW>'
60
70
  StartNbr: '000000'
61
71
  EndNbr: '999999'
62
72
  WarnNbr: '999990'
63
73
  NbrStep: 1
64
74
  StartDate: '1900-01-01'
65
75
  - NumberingID: PORECEIPT
76
+ NewSymbol: '<NEW>'
66
77
  StartNbr: '000000'
67
78
  EndNbr: '999999'
68
79
  WarnNbr: '999990'
69
80
  NbrStep: 1
70
81
  StartDate: '1900-01-01'
71
82
  - NumberingID: SOORDER
83
+ NewSymbol: '<NEW>'
72
84
  StartNbr: '000000'
73
85
  EndNbr: '999999'
74
86
  WarnNbr: '999990'
75
87
  NbrStep: 1
76
88
  StartDate: '1900-01-01'
77
89
  - NumberingID: SOSHIPMENT
90
+ NewSymbol: '<NEW>'
78
91
  StartNbr: '000000'
79
92
  EndNbr: '999999'
80
93
  WarnNbr: '999990'
@@ -1,7 +1,7 @@
1
1
  """Tenant CRUD on the instance.
2
2
 
3
- Thin SSH wrapper around ac.exe CompanyConfig (the only supported CLI route,
4
- see docs/ac-exe.md) plus a sqlcmd read side.
3
+ Thin SSH wrapper around ac.exe CompanyConfig (the only supported CLI route)
4
+ plus a sqlcmd read side.
5
5
  """
6
6
 
7
7
  import subprocess
@@ -15,8 +15,8 @@ class Tenant(Model):
15
15
 
16
16
  login_name is dbo.Company.CompanyKey — the name on the sign-in page and
17
17
  the REST login's tenant value. company_cd is dbo.Company.CompanyCD; ac.exe
18
- auto-generates it (Company2, Company3, …) and ignores LoginName for it
19
- (docs/ac-exe.md). ``acu tenant create`` aligns CompanyCD to the login
18
+ auto-generates it (Company2, Company3, …) and ignores LoginName for it.
19
+ ``acu tenant create`` aligns CompanyCD to the login
20
20
  after CompanyConfig so list Login and CD match.
21
21
  """
22
22
 
@@ -97,8 +97,8 @@ class TenantManager:
97
97
 
98
98
  The tenant map loads at app start; without this, tenants created or
99
99
  deleted by CompanyConfig stay invisible to the sign-in page and REST
100
- routing silently falls back to the default tenant (docs/rest-api.md).
101
- The pool is named after the instance (how acumatica-infra builds it).
100
+ routing silently falls back to the default tenant.
101
+ The pool is named after the instance.
102
102
  """
103
103
  self._ssh(
104
104
  "Import-Module WebAdministration; "
@@ -108,7 +108,7 @@ class TenantManager:
108
108
  def _company_config(self, company: str, extra: str = "") -> str:
109
109
  # -iname AND -h are both required: without -iname CompanyConfig can't
110
110
  # find the site; without -h its web.config step dies on a null path
111
- # (verified 2026-07-08, docs/ac-exe.md).
111
+ # (verified 2026-07-08).
112
112
  return self._ssh(
113
113
  f"& '{AC_EXE}' "
114
114
  f'-configmode:"CompanyConfig" -output:"Forced" '
@@ -132,7 +132,7 @@ class TenantManager:
132
132
  company_type: '' = clean tenant, 'SalesDemo' = demo data. The admin
133
133
  flags preset the tenant's admin to the instance credentials with no
134
134
  must-change flag, so the tenant is REST-loginable right after the
135
- app-pool recycle — no first-login dance (verified, docs/ac-exe.md).
135
+ app-pool recycle — no first-login dance (verified live).
136
136
 
137
137
  Does not set CompanyCD (ac.exe auto-generates it). Call
138
138
  :meth:`set_company_cd` after create to align CD with the login.
@@ -199,7 +199,7 @@ class TenantManager:
199
199
  Identify by ``company_id`` or ``login_name`` (exactly one). Login is
200
200
  the sign-in name / REST tenant value (``CompanyKey``).
201
201
 
202
- Two verified gotchas (docs/ac-exe.md):
202
+ Two verified gotchas:
203
203
 
204
204
  - The sub-key is ``Deleted``, not the officially documented
205
205
  ``Delete`` — an unknown key is silently ignored and the run degrades