acumatica-cli 0.29.0__tar.gz → 0.30.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.29.0 → acumatica_cli-0.30.0}/PKG-INFO +6 -10
  2. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/README.md +5 -9
  3. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/pyproject.toml +1 -1
  4. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/pyproject.toml.orig +1 -1
  5. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/cli.py +4 -192
  6. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/README.md +1 -2
  7. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/__init__.py +0 -0
  8. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/bootstrap.py +0 -0
  9. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/bootstrap_plugin.cs +0 -0
  10. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/bootstrap_project.xml +0 -0
  11. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/client.py +0 -0
  12. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/config.py +0 -0
  13. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/extract.py +0 -0
  14. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/firstlogin.py +0 -0
  15. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/inventory.py +0 -0
  16. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/models.py +0 -0
  17. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/output.py +0 -0
  18. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/reconcile.py +0 -0
  19. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/run.py +0 -0
  20. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/seed.py +0 -0
  21. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/seed_catalog.yaml +0 -0
  22. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/snapshot_map.yaml +0 -0
  23. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/state.py +0 -0
  24. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/baseline/10-subaccounts.yaml +0 -0
  25. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/baseline/20-accounts.yaml +0 -0
  26. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/baseline/40-ledger.yaml +0 -0
  27. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/baseline/50-gl-preferences.yaml +0 -0
  28. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/baseline/60-ledger-company.yaml +0 -0
  29. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/baseline/90-uoms.yaml +0 -0
  30. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/baseline/91-company-packaging.yaml +0 -0
  31. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/bootstrap/company.yaml +0 -0
  32. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/bootstrap/credit-terms.yaml +0 -0
  33. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/bootstrap/features.yaml +0 -0
  34. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/bootstrap/segmented-key.yaml +0 -0
  35. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/05-numbering-sequences.yaml +0 -0
  36. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/10-reason-codes.yaml +0 -0
  37. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/20-in-preferences.yaml +0 -0
  38. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/30-availability-rules.yaml +0 -0
  39. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/40-posting-classes.yaml +0 -0
  40. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/50-warehouse.yaml +0 -0
  41. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/51-warehouse-locations.yaml +0 -0
  42. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/52-warehouse-defaults.yaml +0 -0
  43. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/53-tax-categories.yaml +0 -0
  44. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/54-item-classes.yaml +0 -0
  45. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/56-so-preferences.yaml +0 -0
  46. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/57-po-preferences.yaml +0 -0
  47. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/58-order-types.yaml +0 -0
  48. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +0 -0
  49. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +0 -0
  50. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/62-ca-preferences.yaml +0 -0
  51. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/63-cash-account.yaml +0 -0
  52. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/64-payment-methods.yaml +0 -0
  53. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/65-statement-cycles.yaml +0 -0
  54. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/70-vendor-classes.yaml +0 -0
  55. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/71-customer-classes.yaml +0 -0
  56. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/75-vendors.yaml +0 -0
  57. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/76-customers.yaml +0 -0
  58. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/80-stock-items-parts.yaml +0 -0
  59. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/82-stock-items-kits.yaml +0 -0
  60. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/85-kit-specifications.yaml +0 -0
  61. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/90-roles.yaml +0 -0
  62. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/91-users.yaml +0 -0
  63. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/master/92-role-users.yaml +0 -0
  64. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/setup/10-financial-year.yaml +0 -0
  65. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/setup/20-master-calendar.yaml +0 -0
  66. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/setup/30-open-periods.yaml +0 -0
  67. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/config/views/10-trial-balance.yaml +0 -0
  68. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/env +0 -0
  69. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/gitignore +0 -0
  70. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/overlays/README.md +0 -0
  71. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/overlays/default-24.200.001/README.md +0 -0
  72. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/overlays/default-24.200.001/scenario/30-build.yaml +0 -0
  73. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/scenario/10-seed-capital.yaml +0 -0
  74. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/scenario/20-buy.yaml +0 -0
  75. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/scenario/30-build.yaml +0 -0
  76. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/templates/scenario/40-sell.yaml +0 -0
  77. {acumatica_cli-0.29.0 → acumatica_cli-0.30.0}/src/acumatica_cli/tenant.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: acumatica-cli
3
- Version: 0.29.0
3
+ Version: 0.30.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>
@@ -45,7 +45,7 @@ YAML in git is the source of truth; the live tenant is the target. `apply` is th
45
45
  | Capture derived balances | `state` | Inquire trial-balance etc. into `state/` (not seed) |
46
46
  | Create and destroy tenants | `tenant` | SSH control plane: list / create / delete / recycle |
47
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 |
48
+ | Prove a cold rebuild | `tenant create` then `apply` then `run` then `diff` | Compose on a fresh tenant. No wrap command. |
49
49
  | Snapshot a site offline | `inventory` | SM203520 XML ZIP or `ac.exe export xml` writes `inventory/` |
50
50
  | Cross-check snapshot vs seed | `reconcile` | `inventory/` plus optional `config/` writes `findings/` only |
51
51
  | Dump the contract schema | `schema` | OpenAPI `swagger.json` for the pinned endpoint |
@@ -76,7 +76,6 @@ acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup
76
76
  acu --tenant DEV run scenario/ # once capital → buy → build → sell
77
77
  acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
78
78
  acu --tenant DEV state # capture state/ trial-balance
79
- acu check --yes --tenant DEV # cold lifecycle create→apply→run→diff (leave tenant)
80
79
  ```
81
80
 
82
81
  Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
@@ -115,7 +114,6 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
115
114
  ├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
116
115
  ├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
117
116
  ├── run [--dry-run] [FILES...] execute transaction scenario YAML (exit 1 on any miss)
118
- ├── check [--yes] [--tenant L] cold lifecycle create→apply→run→diff; leave tenant (V47)
119
117
  ├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
120
118
  │ capture derived state into state/ (not seed)
121
119
  ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
@@ -285,7 +283,6 @@ Ad-hoc override: `acu --api-version 24.200.001 …` (version half only, never
285
283
  `base_url` resolves as `--url`, else `ACU_BASE_URL`, else a hard error naming sources.
286
284
 
287
285
  `acu config show` prints the resolved `.env` (password excluded; includes `ACU_API_VERSION`).
288
- `acu check` (lifecycle) uses the same resolved instance.
289
286
 
290
287
  ### Multi-host overlays (V44)
291
288
 
@@ -317,9 +314,6 @@ acu diff
317
314
  # explicit path args (no auto) — later path wins same keys
318
315
  acu apply config/ overlays/default-24.200.001/
319
316
  acu diff config/ overlays/default-24.200.001/
320
-
321
- # cold lifecycle vs the resolved .env instance (SSH + tenant required)
322
- acu check --yes --tenant LAB5
323
317
  ```
324
318
 
325
319
  Add a future half by creating `overlays/default-<new-half>/` with the
@@ -335,7 +329,7 @@ Worth knowing:
335
329
  - Without a `.env`, global flags plus the process environment supply the configuration.
336
330
  - When `ACU_SSH` is **absent**, acu defaults to `Administrator@` + the resolved base_url hostname.
337
331
  A **present blank** `ACU_SSH=` is the hosted opt-out.
338
- Only `acu tenant` / `acu check` require a non-empty value post-default.
332
+ Only `acu tenant` requires a non-empty value post-default.
339
333
  - `acu config show` prints the resolved `.env` (password excluded).
340
334
  - Redirect it to turn resolved state into a working config: `acu config show > .env`.
341
335
 
@@ -383,7 +377,9 @@ gmake release patch # or minor | major
383
377
  2. Fail if `## Unreleased` has no bullets
384
378
  3. Bump `pyproject.toml` version (`major` | `minor` | `patch`)
385
379
  4. Promote Unreleased body to `## [vX.Y.Z] - YYYY-MM-DD`, leave an empty `## Unreleased`
386
- 5. Commit `CHANGELOG.md` + `pyproject.toml` (+ lock if bumped) together, tag `vX.Y.Z`, push
380
+ 5. Commit `CHANGELOG.md` + `pyproject.toml` (+ lock if bumped) together, tag `vX.Y.Z`
381
+ 6. `gmake check` again on the promoted tree
382
+ 7. Push tag + branch — GitHub Actions publishes
387
383
 
388
384
  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).
389
385
 
@@ -27,7 +27,7 @@ YAML in git is the source of truth; the live tenant is the target. `apply` is th
27
27
  | Capture derived balances | `state` | Inquire trial-balance etc. into `state/` (not seed) |
28
28
  | Create and destroy tenants | `tenant` | SSH control plane: list / create / delete / recycle |
29
29
  | Publish the Bootstrap contract | `bootstrap` | REST publish of AcuBootstrap (or `--export` zip for the UI) |
30
- | Prove a cold rebuild | `check` | create, then apply, then run, then diff on a fresh tenant |
30
+ | Prove a cold rebuild | `tenant create` then `apply` then `run` then `diff` | Compose on a fresh tenant. No wrap command. |
31
31
  | Snapshot a site offline | `inventory` | SM203520 XML ZIP or `ac.exe export xml` writes `inventory/` |
32
32
  | Cross-check snapshot vs seed | `reconcile` | `inventory/` plus optional `config/` writes `findings/` only |
33
33
  | Dump the contract schema | `schema` | OpenAPI `swagger.json` for the pinned endpoint |
@@ -58,7 +58,6 @@ acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup
58
58
  acu --tenant DEV run scenario/ # once capital → buy → build → sell
59
59
  acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
60
60
  acu --tenant DEV state # capture state/ trial-balance
61
- acu check --yes --tenant DEV # cold lifecycle create→apply→run→diff (leave tenant)
62
61
  ```
63
62
 
64
63
  Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
@@ -97,7 +96,6 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
97
96
  ├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
98
97
  ├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
99
98
  ├── run [--dry-run] [FILES...] execute transaction scenario YAML (exit 1 on any miss)
100
- ├── check [--yes] [--tenant L] cold lifecycle create→apply→run→diff; leave tenant (V47)
101
99
  ├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
102
100
  │ capture derived state into state/ (not seed)
103
101
  ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
@@ -267,7 +265,6 @@ Ad-hoc override: `acu --api-version 24.200.001 …` (version half only, never
267
265
  `base_url` resolves as `--url`, else `ACU_BASE_URL`, else a hard error naming sources.
268
266
 
269
267
  `acu config show` prints the resolved `.env` (password excluded; includes `ACU_API_VERSION`).
270
- `acu check` (lifecycle) uses the same resolved instance.
271
268
 
272
269
  ### Multi-host overlays (V44)
273
270
 
@@ -299,9 +296,6 @@ acu diff
299
296
  # explicit path args (no auto) — later path wins same keys
300
297
  acu apply config/ overlays/default-24.200.001/
301
298
  acu diff config/ overlays/default-24.200.001/
302
-
303
- # cold lifecycle vs the resolved .env instance (SSH + tenant required)
304
- acu check --yes --tenant LAB5
305
299
  ```
306
300
 
307
301
  Add a future half by creating `overlays/default-<new-half>/` with the
@@ -317,7 +311,7 @@ Worth knowing:
317
311
  - Without a `.env`, global flags plus the process environment supply the configuration.
318
312
  - When `ACU_SSH` is **absent**, acu defaults to `Administrator@` + the resolved base_url hostname.
319
313
  A **present blank** `ACU_SSH=` is the hosted opt-out.
320
- Only `acu tenant` / `acu check` require a non-empty value post-default.
314
+ Only `acu tenant` requires a non-empty value post-default.
321
315
  - `acu config show` prints the resolved `.env` (password excluded).
322
316
  - Redirect it to turn resolved state into a working config: `acu config show > .env`.
323
317
 
@@ -365,7 +359,9 @@ gmake release patch # or minor | major
365
359
  2. Fail if `## Unreleased` has no bullets
366
360
  3. Bump `pyproject.toml` version (`major` | `minor` | `patch`)
367
361
  4. Promote Unreleased body to `## [vX.Y.Z] - YYYY-MM-DD`, leave an empty `## Unreleased`
368
- 5. Commit `CHANGELOG.md` + `pyproject.toml` (+ lock if bumped) together, tag `vX.Y.Z`, push
362
+ 5. Commit `CHANGELOG.md` + `pyproject.toml` (+ lock if bumped) together, tag `vX.Y.Z`
363
+ 6. `gmake check` again on the promoted tree
364
+ 7. Push tag + branch — GitHub Actions publishes
369
365
 
370
366
  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).
371
367
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acumatica-cli"
3
- version = "0.29.0"
3
+ version = "0.30.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.29.0"
3
+ version = "0.30.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"
@@ -55,7 +55,7 @@ COMMAND MAP (pick by intent)
55
55
  seed write apply [--dry-run] [FILES...] (sole mutator)
56
56
  seed check diff [FILES...] (exit 2 on drift)
57
57
  txns run [--dry-run] [FILES...]
58
- cold CI check [--tenant LOGIN] (!= config check)
58
+ cold rebuild tenant create then apply then run then diff (no wrap cmd)
59
59
  pull seed extract [--only ENTITY]... (inverse of apply)
60
60
  observe state [--diff|--assert-unchanged]
61
61
  offline inventory ARTIFACT | reconcile
@@ -66,7 +66,6 @@ EXIT CODES (common)
66
66
  0 success
67
67
  1 operational / parse / expectation failure
68
68
  2 seed drift (diff) or state moved (state --assert-unchanged)
69
- check (lifecycle) never exits 2 — drift fails the run as exit 1
70
69
 
71
70
  \b
72
71
  DEFAULT PATHS (when FILES omitted)
@@ -674,8 +673,8 @@ def config_group() -> None:
674
673
  show print resolved config as a complete .env (password redacted)
675
674
  check read-only live preflight (REST + optional SSH)
676
675
 
677
- Not the same as `acu check` (cold lifecycle rebuild). Start here on a
678
- new machine: init → edit .env → config check → tenant create / bootstrap.
676
+ Start here on a new machine: init → edit .env → config check →
677
+ tenant create / bootstrap.
679
678
  """
680
679
 
681
680
 
@@ -721,7 +720,6 @@ def config_init(host: str | None, directory: Path | None) -> None:
721
720
  output.data(" 5. acu run scenario/")
722
721
  output.data(" 6. acu diff config/")
723
722
  output.data(" 7. acu state")
724
- output.data(" 8. acu check # cold lifecycle (SSH + tenant)")
725
723
 
726
724
 
727
725
  @config_group.command("show")
@@ -751,7 +749,7 @@ def config_check(ctx: click.Context) -> None:
751
749
  """Read-only preflight of the resolved target (ok/fail/warn/skip lines).
752
750
 
753
751
  Proves connectivity before apply/run. Writes nothing (no PUT, no tenant
754
- CRUD). Not `acu check` (that is a destructive cold lifecycle rebuild).
752
+ CRUD).
755
753
 
756
754
  \b
757
755
  Probe order
@@ -875,192 +873,6 @@ def _probe_endpoints(endpoints: list[tuple[str, str]], inst: Instance) -> bool:
875
873
  return False
876
874
 
877
875
 
878
- @cli.command("check")
879
- @click.option(
880
- "--yes",
881
- is_flag=True,
882
- help="Accepted for muscle-memory (pre-clean delete is always unattended)",
883
- )
884
- @click.option(
885
- "--tenant",
886
- "tenant_login",
887
- default=None,
888
- help="Tenant login for pre-clean create (else ACU_TENANT / global --tenant)",
889
- )
890
- @click.pass_context
891
- def check_cmd(ctx: click.Context, yes: bool, tenant_login: str | None) -> None:
892
- """Cold lifecycle: delete → create → apply → run → diff (leave tenant).
893
-
894
- Destructive rebuild for CI against the resolved .env instance. Not
895
- `acu config check` (read-only preflight). Requires non-empty ACU_SSH
896
- and a tenant login (--tenant / ACU_TENANT). Pre-clean delete is
897
- always unattended; rebuilt tenant is left on the host for inspect /
898
- state. --yes is accepted for muscle-memory only.
899
-
900
- \b
901
- Flow
902
- delete tenant (if present) → create+bootstrap → apply → run → diff
903
- Diff drift fails the run as exit 1 (never exit 2 from this command).
904
-
905
- \b
906
- Examples
907
- acu check --tenant DEV
908
- acu check --yes --tenant DEV
909
-
910
- Prefer `config check` for "can I connect?" Prefer this for "rebuild
911
- green from empty."
912
- """
913
- _ = yes
914
- overrides: dict[str, str] = dict(ctx.obj or {})
915
- tenant = _resolve_check_tenant(tenant_login, overrides)
916
- try:
917
- inst = load_instance({**overrides, "tenant": tenant})
918
- except SystemExit as exc:
919
- raise SystemExit(str(exc)) from exc
920
- if not inst.ssh:
921
- raise SystemExit(
922
- "ACU_SSH not set (pass --ssh, or put ACU_SSH in .env or the environment)"
923
- )
924
- output.data(f"check {inst.base_url} (api={inst.api_version})")
925
- if _run_lifecycle(inst, tenant) != 0:
926
- raise SystemExit(1)
927
- output.success("check: green")
928
-
929
-
930
- def _resolve_check_tenant(tenant_login: str | None, overrides: dict[str, str]) -> str:
931
- """Tenant login from flag, global, env, or .env; hard-error if unset."""
932
- tenant = tenant_login or overrides.get("tenant") or os.environ.get("ACU_TENANT")
933
- if not tenant and (root := find_data_root()) is not None:
934
- tenant = read_env_values(root / ".env").get("tenant") or None
935
- if not tenant:
936
- raise SystemExit(
937
- "tenant not set (pass --tenant, "
938
- "or put ACU_TENANT in .env or the environment)"
939
- )
940
- return tenant
941
-
942
-
943
- def _run_lifecycle(inst: Instance, tenant: str) -> int:
944
- """pre-clean→create→apply→run→diff; leave tenant (V47); return 0 or 1."""
945
- mgr = TenantManager(inst)
946
- _lifecycle_delete_best_effort(mgr, tenant, label="pre-clean")
947
- try:
948
- _lifecycle_create_and_bootstrap(inst, mgr, tenant)
949
- data_inst = inst.model_copy(update={"tenant": tenant})
950
- _lifecycle_apply(data_inst)
951
- _lifecycle_run(data_inst)
952
- _lifecycle_diff(data_inst)
953
- except SystemExit as exc:
954
- code = exc.code if isinstance(exc.code, int) else 1
955
- if code == 2:
956
- code = 1 # V47: check never exits 2
957
- output.error(f"lifecycle: {exc}")
958
- # Leave tenant for manual inspect (no post-clean — V47).
959
- return 1 if code != 0 else 0
960
- except (RuntimeError, httpx.HTTPError) as exc:
961
- output.error(f"lifecycle: {_format_failure(exc, target=inst.base_url)}")
962
- # Leave tenant for manual inspect (no post-clean — V47).
963
- return 1
964
- output.success(
965
- f"lifecycle green on {inst.base_url} tenant {tenant} (left for inspection)"
966
- )
967
- return 0
968
-
969
-
970
- def _lifecycle_create_and_bootstrap(
971
- inst: Instance, mgr: TenantManager, tenant: str
972
- ) -> None:
973
- """Create (or exists-skip) tenant + init + bootstrap chain."""
974
- tenants = mgr.list()
975
- existing = next((t for t in tenants if t.login_name == tenant), None)
976
- if existing is not None:
977
- company_id = existing.company_id
978
- output.data(f"skip create: tenant {tenant} exists (id {company_id})")
979
- else:
980
- company_id = max((t.company_id for t in tenants), default=0) + 1
981
- with output.step(f"creating tenant {company_id} ({tenant}) on {inst.base_url}"):
982
- mgr.create(company_id, tenant, 1, True, "")
983
- output.data("created")
984
- with output.step(f"aligning CompanyCD to login ({tenant})"):
985
- if mgr.set_company_cd(company_id, tenant):
986
- output.data(f"CompanyCD set to {tenant}")
987
- else:
988
- output.data(f"CompanyCD already {tenant}")
989
- _init_tenant(inst, mgr, tenant)
990
- _bootstrap_tenant(inst, mgr, tenant)
991
-
992
-
993
- def _lifecycle_delete_best_effort(
994
- mgr: TenantManager, tenant: str, *, label: str
995
- ) -> None:
996
- try:
997
- tenants = mgr.list()
998
- if any(t.login_name == tenant for t in tenants):
999
- with output.step(f"{label} delete tenant {tenant}"):
1000
- mgr.delete(login_name=tenant)
1001
- with output.step(f"recycling app pool after {label}"):
1002
- mgr.recycle_app_pool()
1003
- output.data(f"{label}: deleted {tenant}")
1004
- else:
1005
- output.data(f"{label}: tenant {tenant} absent")
1006
- except (RuntimeError, SystemExit) as exc:
1007
- output.warn(f"{label}: {exc}")
1008
-
1009
-
1010
- def _lifecycle_apply(inst: Instance) -> None:
1011
- """Bare apply (pin overlay auto-compose); SystemExit 1 on failure."""
1012
- total_ok = 0
1013
- all_errors: list[str] = []
1014
- with AcumaticaClient(inst) as client:
1015
- for path in expand_files(default_apply_dirs(inst)):
1016
- baseline = seed.load_baseline(path)
1017
- output.data(
1018
- f"{path} -> {inst.tenant} on {inst.base_url} ({baseline.entity})"
1019
- )
1020
- n, errors = seed.apply(client, baseline, dry_run=False)
1021
- total_ok += n
1022
- all_errors.extend(f"{path}: {e}" for e in errors)
1023
- output.data(f" {n} record(s)")
1024
- if all_errors:
1025
- output.error(f"apply: {len(all_errors)} error(s), {total_ok} record(s) applied")
1026
- for err in all_errors:
1027
- output.error(err)
1028
- raise SystemExit(1)
1029
-
1030
-
1031
- def _lifecycle_run(inst: Instance) -> None:
1032
- """Bare run (pin overlay); SystemExit 1 on failure."""
1033
- paths = list(default_scenario_files(inst))
1034
- scenarios = [run.load_scenario(path) for path in paths]
1035
- ok = True
1036
- with AcumaticaClient(inst) as client:
1037
- for scenario in scenarios:
1038
- ok = run.run(client, scenario) and ok
1039
- if not ok:
1040
- raise SystemExit(1)
1041
- output.success(f"{len(scenarios)} scenario(s) passed on {inst.tenant}")
1042
-
1043
-
1044
- def _lifecycle_diff(inst: Instance) -> None:
1045
- """Bare diff; SystemExit 2 on drift (caller maps to 1 for check)."""
1046
- paths = expand_files(default_apply_dirs(inst))
1047
- drifts: list[str] = []
1048
- with AcumaticaClient(inst) as client:
1049
- for path in paths:
1050
- baseline = seed.load_baseline(path)
1051
- output.data(
1052
- f"{path} -> {inst.tenant} on {inst.base_url} ({baseline.entity})"
1053
- )
1054
- file_drifts = seed.diff(client, baseline)
1055
- drifts += file_drifts
1056
- n = 1 if isinstance(baseline, seed.ActionFile) else len(baseline.records)
1057
- if file_drifts:
1058
- output.data(f" {n} record(s), {len(file_drifts)} drift(s)")
1059
- else:
1060
- output.data(f" {n} record(s) ok")
1061
- _exit_on_drift(inst, drifts, len(paths))
1062
-
1063
-
1064
876
  SEED_DIRS = ("bootstrap", "baseline", "setup", "master")
1065
877
 
1066
878
 
@@ -30,8 +30,7 @@ acu state
30
30
  # warm gate: once-capital only — additive buy/sell moves numeric observations
31
31
  acu run scenario/10-seed-capital.yaml && acu state --assert-unchanged
32
32
 
33
- # 7. Cold lifecycle (SSH + tenant)
34
- # acu check --yes
33
+ # SSH cold rebuild: acu tenant create --login NAME, then apply, run, diff
35
34
 
36
35
  # Optional: re-seed from live (inverse of apply; always under config/)
37
36
  # acu extract --out . --force