acumatica-cli 0.13.1__tar.gz → 0.15.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 (68) hide show
  1. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/PKG-INFO +24 -12
  2. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/README.md +23 -11
  3. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/pyproject.toml +1 -1
  4. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/cli.py +34 -35
  5. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/client.py +1 -1
  6. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/config.py +2 -6
  7. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/extract.py +194 -52
  8. acumatica_cli-0.15.0/src/acumatica_cli/seed_catalog.yaml +450 -0
  9. acumatica_cli-0.13.1/src/acumatica_cli/snapshot.py → acumatica_cli-0.15.0/src/acumatica_cli/state.py +9 -9
  10. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/README.md +9 -5
  11. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/80-stock-items-parts.yaml +2 -21
  12. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/82-stock-items-kits.yaml +2 -9
  13. {acumatica_cli-0.13.1/src/acumatica_cli/templates/config/snapshot → acumatica_cli-0.15.0/src/acumatica_cli/templates/config/views}/10-trial-balance.yaml +2 -2
  14. acumatica_cli-0.13.1/src/acumatica_cli/extract_manifest.yaml +0 -152
  15. acumatica_cli-0.13.1/src/acumatica_cli/templates/config/baseline/91-company-packaging.yaml +0 -14
  16. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/__init__.py +0 -0
  17. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/bootstrap.py +0 -0
  18. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/bootstrap_plugin.cs +0 -0
  19. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/bootstrap_project.xml +0 -0
  20. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/firstlogin.py +0 -0
  21. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/models.py +0 -0
  22. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/output.py +0 -0
  23. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/run.py +0 -0
  24. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/seed.py +0 -0
  25. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/target.py +0 -0
  26. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/baseline/10-subaccounts.yaml +0 -0
  27. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/baseline/20-accounts.yaml +0 -0
  28. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/baseline/40-ledger.yaml +0 -0
  29. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/baseline/50-gl-preferences.yaml +0 -0
  30. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/baseline/60-ledger-company.yaml +0 -0
  31. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/baseline/90-uoms.yaml +0 -0
  32. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/bootstrap/company.yaml +0 -0
  33. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/bootstrap/credit-terms.yaml +0 -0
  34. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/bootstrap/features.yaml +0 -0
  35. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/10-reason-codes.yaml +0 -0
  36. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/20-in-preferences.yaml +0 -0
  37. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/30-availability-rules.yaml +0 -0
  38. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/40-posting-classes.yaml +0 -0
  39. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/50-warehouse.yaml +0 -0
  40. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/51-warehouse-locations.yaml +0 -0
  41. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/52-warehouse-defaults.yaml +0 -0
  42. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/53-tax-categories.yaml +0 -0
  43. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/54-item-classes.yaml +0 -0
  44. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/56-so-preferences.yaml +0 -0
  45. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/57-po-preferences.yaml +0 -0
  46. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/58-order-types.yaml +0 -0
  47. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +0 -0
  48. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +0 -0
  49. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/62-ca-preferences.yaml +0 -0
  50. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/63-cash-account.yaml +0 -0
  51. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/64-payment-methods.yaml +0 -0
  52. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/65-statement-cycles.yaml +0 -0
  53. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/70-vendor-classes.yaml +0 -0
  54. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/71-customer-classes.yaml +0 -0
  55. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/75-vendors.yaml +0 -0
  56. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/76-customers.yaml +0 -0
  57. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/master/85-kit-specifications.yaml +0 -0
  58. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/setup/10-financial-year.yaml +0 -0
  59. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/setup/20-master-calendar.yaml +0 -0
  60. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/config/setup/30-open-periods.yaml +0 -0
  61. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/env +0 -0
  62. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/gitignore +0 -0
  63. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/scenario/10-seed-capital.yaml +0 -0
  64. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/scenario/20-buy.yaml +0 -0
  65. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/scenario/30-build.yaml +0 -0
  66. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/scenario/40-sell.yaml +0 -0
  67. {acumatica_cli-0.13.1 → acumatica_cli-0.15.0}/src/acumatica_cli/templates/target +0 -0
  68. {acumatica_cli-0.13.1 → acumatica_cli-0.15.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.13.1
3
+ Version: 0.15.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>
@@ -48,7 +48,7 @@ acu tenant create --id 3 --login DEV # create the tenant + bootstrap it (nee
48
48
  acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup,master}/
49
49
  acu --tenant DEV run scenario/ # once capital → buy → build → sell
50
50
  acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
51
- acu --tenant DEV snapshot # capture state/ trial-balance
51
+ acu --tenant DEV state # capture state/ trial-balance
52
52
  # warm: capital once-skips; Owner Capital stays 50000 (not 100000)
53
53
  acu --tenant DEV run scenario/
54
54
  ```
@@ -85,10 +85,10 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
85
85
  ├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
86
86
  ├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
87
87
  ├── run [--dry-run] [FILES...] execute transaction scenario YAML (exit 1 on any miss)
88
- ├── snapshot [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
88
+ ├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
89
89
  │ capture derived state into state/ (not seed)
90
90
  ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
91
- │ dump live tenant state as seed YAML (inverse of apply)
91
+ │ inverse of apply into config/{bootstrap,baseline,setup,master}/
92
92
  ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
93
93
  │
94
94
  └── config configuration ops
@@ -100,23 +100,24 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
100
100
  `apply` and `diff` without FILES prefer `config/<name>/` when any seed child exists under `config/`; otherwise root `bootstrap/`, `baseline/`, `setup/`, then `master/` when present.
101
101
  A path like `config/` expands nested seed dirs in that fixed order.
102
102
  `run` without FILES defaults to `scenario/`.
103
- `snapshot` without FILES defaults to `config/snapshot/`; writes go to `state/` (`--out`).
103
+ `state` without FILES defaults to `config/views/`; writes go to `state/` (`--out`).
104
+ `extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
104
105
  `acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
105
106
  Run `acu <command> --help` for details on any command.
106
107
 
107
108
  ## The data repo
108
109
 
109
110
  Your configuration lives in its own git repo.
110
- `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/snapshot/`, and README. There is no `--flavor`.
111
+ `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. There is no `--flavor`.
111
112
 
112
113
  | Path | What it holds |
113
114
  | ---- | ------------- |
114
115
  | `config/bootstrap/` | virgin-tenant config: features, company, credit terms, `project.xml` |
115
- | `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs, packaging |
116
+ | `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs |
116
117
  | `config/setup/` | one-time actions: financial year, master calendar, open periods |
117
118
  | `config/master/` | inventory/distribution masters (prefs, warehouse, items, parties) |
118
119
  | `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
119
- | `config/snapshot/` | observer views for `acu snapshot` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
120
+ | `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
120
121
  | `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
121
122
  | `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
122
123
  | `.env` | where to apply and who signs in, every key an `ACU_*` variable |
@@ -128,13 +129,24 @@ The scaffolded `.gitignore` keeps `.env` out of git — store it encrypted (for
128
129
  Commit `target.yaml` with the seeds so every clone knows the verified ERP line and Default API generation.
129
130
 
130
131
  Seed YAML is state: `apply` upserts it, `diff` proves it.
131
- Scenario YAML is different — it describes transactions that flow forward.
132
+ `acu extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut). 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. Features synthesize to `config/bootstrap/features.yaml`. Existing files skip unless `--force`; empty live sets skip; row failures continue (exit 1 only if any row failed — drift stays with `diff`).
133
+
134
+ ```sh
135
+ acu --tenant DEV extract --out . --force # refresh config/** from live tenant
136
+ git diff config/ # review extract delta before commit
137
+ acu --tenant DEV apply config/ # replay extracted seed
138
+ acu --tenant DEV diff config/ # expect exit 0
139
+ ```
140
+
141
+ **Migration (T115–T120 extract hard-cut):** extract no longer writes root `bootstrap/` / `baseline/` / `setup/` / `master/`. Paths are always `config/…`. There is no `--layout`. Move any root-layout extract output under `config/`, or re-extract into a modern data repo. Catalog rename: `extract_manifest.yaml` becomes `seed_catalog.yaml` (package data only; operators do not author it).
142
+
143
+ Scenario YAML is different — it describes transactions that flow forward (not extractable seed).
132
144
  `acu run` executes each step in order (`put`, `action`, `wait`, `get`), captures server-assigned document numbers into `${var}` references for later steps, and checks `expect:` assertions as deltas against a pre-run snapshot, so additive scenarios re-run safely on a warm tenant.
133
145
  `once: true` scenarios declare a `present` inquire-absolute gate; when the probe already holds, the CLI prints `skip <path> (once: already present)` and runs neither steps nor expects (Owner Capital does not restack).
134
146
 
135
- `acu snapshot` is the third observation path: it captures live derived state (balances) into `state/` for git review. It is not `extract` (config seed) and not `diff` (desired vs actual config). Packaged golden is trial-balance via contract `inquire:` (`EndingBalance` fixed-point); inventory-summary is not golden this pass. `gi:` stays optional when a GI is V12-verified and **Expose via OData** is on (`params` fail-closed vs `$metadata`). After a cold `acu run scenario/ && acu snapshot`, warm `acu run scenario/10-seed-capital.yaml && acu snapshot --assert-unchanged` is the once-class gate (full scenario re-run is additive and moves cash observations on the TB).
147
+ `acu state` is the third observation path: it captures live derived state (balances) into `state/` for git review. It is not `extract` (config seed) and not `diff` (desired vs actual config). Packaged golden is trial-balance via contract `inquire:` (`EndingBalance` fixed-point); inventory-summary is not golden this pass. `gi:` stays optional when a GI is V12-verified and **Expose via OData** is on (`params` fail-closed vs `$metadata`). After a cold `acu run scenario/ && acu state`, warm `acu run scenario/10-seed-capital.yaml && acu state --assert-unchanged` is the once-class gate (full scenario re-run is additive and moves cash observations on the TB).
136
148
 
137
- **Migration (path hard-cut):** bare defaults are `config/snapshot/` (views) and `state/` (observations). Root `snapshot/` and `snapshots/` are no longer defaulted — move files or pass explicit path args.
149
+ **Migration (T112–T114 hard-cut):** CLI verb is `state` only (no `snapshot` alias). Bare defaults are `config/views/` (views) and `state/` (observations). Prior `config/snapshot/` and root `snapshot/` / `snapshots/` are not fallbacks — move views to `config/views/` or pass explicit path args.
138
150
 
139
151
  ### Seed `endpoint:` symbols
140
152
 
@@ -308,7 +320,7 @@ The packaged full `config init` seed (under `config/`) is the only scaffold.
308
320
  ```sh
309
321
  gmake e2e # whole tier, about 20 minutes
310
322
  gmake e2e FILE=test_provision_lifecycle # apply/diff focus
311
- gmake e2e FILE=test_scenario_lifecycle # scenario + snapshot focus
323
+ gmake e2e FILE=test_scenario_lifecycle # scenario + state focus
312
324
  ```
313
325
 
314
326
  ## License
@@ -30,7 +30,7 @@ acu tenant create --id 3 --login DEV # create the tenant + bootstrap it (nee
30
30
  acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup,master}/
31
31
  acu --tenant DEV run scenario/ # once capital → buy → build → sell
32
32
  acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
33
- acu --tenant DEV snapshot # capture state/ trial-balance
33
+ acu --tenant DEV state # capture state/ trial-balance
34
34
  # warm: capital once-skips; Owner Capital stays 50000 (not 100000)
35
35
  acu --tenant DEV run scenario/
36
36
  ```
@@ -67,10 +67,10 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
67
67
  ├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
68
68
  ├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
69
69
  ├── run [--dry-run] [FILES...] execute transaction scenario YAML (exit 1 on any miss)
70
- ├── snapshot [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
70
+ ├── state [--out DIR] [--diff] [--assert-unchanged] [--dry-run] [FILES...]
71
71
  │ capture derived state into state/ (not seed)
72
72
  ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
73
- │ dump live tenant state as seed YAML (inverse of apply)
73
+ │ inverse of apply into config/{bootstrap,baseline,setup,master}/
74
74
  ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
75
75
  │
76
76
  └── config configuration ops
@@ -82,23 +82,24 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
82
82
  `apply` and `diff` without FILES prefer `config/<name>/` when any seed child exists under `config/`; otherwise root `bootstrap/`, `baseline/`, `setup/`, then `master/` when present.
83
83
  A path like `config/` expands nested seed dirs in that fixed order.
84
84
  `run` without FILES defaults to `scenario/`.
85
- `snapshot` without FILES defaults to `config/snapshot/`; writes go to `state/` (`--out`).
85
+ `state` without FILES defaults to `config/views/`; writes go to `state/` (`--out`).
86
+ `extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
86
87
  `acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
87
88
  Run `acu <command> --help` for details on any command.
88
89
 
89
90
  ## The data repo
90
91
 
91
92
  Your configuration lives in its own git repo.
92
- `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/snapshot/`, and README. There is no `--flavor`.
93
+ `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. There is no `--flavor`.
93
94
 
94
95
  | Path | What it holds |
95
96
  | ---- | ------------- |
96
97
  | `config/bootstrap/` | virgin-tenant config: features, company, credit terms, `project.xml` |
97
- | `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs, packaging |
98
+ | `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs |
98
99
  | `config/setup/` | one-time actions: financial year, master calendar, open periods |
99
100
  | `config/master/` | inventory/distribution masters (prefs, warehouse, items, parties) |
100
101
  | `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
101
- | `config/snapshot/` | observer views for `acu snapshot` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
102
+ | `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
102
103
  | `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
103
104
  | `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
104
105
  | `.env` | where to apply and who signs in, every key an `ACU_*` variable |
@@ -110,13 +111,24 @@ The scaffolded `.gitignore` keeps `.env` out of git — store it encrypted (for
110
111
  Commit `target.yaml` with the seeds so every clone knows the verified ERP line and Default API generation.
111
112
 
112
113
  Seed YAML is state: `apply` upserts it, `diff` proves it.
113
- Scenario YAML is different — it describes transactions that flow forward.
114
+ `acu extract` is the inverse of `apply`: GET live tenant rows into seed YAML under `config/{bootstrap,baseline,setup,master}/` (hard-cut). 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. Features synthesize to `config/bootstrap/features.yaml`. Existing files skip unless `--force`; empty live sets skip; row failures continue (exit 1 only if any row failed — drift stays with `diff`).
115
+
116
+ ```sh
117
+ acu --tenant DEV extract --out . --force # refresh config/** from live tenant
118
+ git diff config/ # review extract delta before commit
119
+ acu --tenant DEV apply config/ # replay extracted seed
120
+ acu --tenant DEV diff config/ # expect exit 0
121
+ ```
122
+
123
+ **Migration (T115–T120 extract hard-cut):** extract no longer writes root `bootstrap/` / `baseline/` / `setup/` / `master/`. Paths are always `config/…`. There is no `--layout`. Move any root-layout extract output under `config/`, or re-extract into a modern data repo. Catalog rename: `extract_manifest.yaml` becomes `seed_catalog.yaml` (package data only; operators do not author it).
124
+
125
+ Scenario YAML is different — it describes transactions that flow forward (not extractable seed).
114
126
  `acu run` executes each step in order (`put`, `action`, `wait`, `get`), captures server-assigned document numbers into `${var}` references for later steps, and checks `expect:` assertions as deltas against a pre-run snapshot, so additive scenarios re-run safely on a warm tenant.
115
127
  `once: true` scenarios declare a `present` inquire-absolute gate; when the probe already holds, the CLI prints `skip <path> (once: already present)` and runs neither steps nor expects (Owner Capital does not restack).
116
128
 
117
- `acu snapshot` is the third observation path: it captures live derived state (balances) into `state/` for git review. It is not `extract` (config seed) and not `diff` (desired vs actual config). Packaged golden is trial-balance via contract `inquire:` (`EndingBalance` fixed-point); inventory-summary is not golden this pass. `gi:` stays optional when a GI is V12-verified and **Expose via OData** is on (`params` fail-closed vs `$metadata`). After a cold `acu run scenario/ && acu snapshot`, warm `acu run scenario/10-seed-capital.yaml && acu snapshot --assert-unchanged` is the once-class gate (full scenario re-run is additive and moves cash observations on the TB).
129
+ `acu state` is the third observation path: it captures live derived state (balances) into `state/` for git review. It is not `extract` (config seed) and not `diff` (desired vs actual config). Packaged golden is trial-balance via contract `inquire:` (`EndingBalance` fixed-point); inventory-summary is not golden this pass. `gi:` stays optional when a GI is V12-verified and **Expose via OData** is on (`params` fail-closed vs `$metadata`). After a cold `acu run scenario/ && acu state`, warm `acu run scenario/10-seed-capital.yaml && acu state --assert-unchanged` is the once-class gate (full scenario re-run is additive and moves cash observations on the TB).
118
130
 
119
- **Migration (path hard-cut):** bare defaults are `config/snapshot/` (views) and `state/` (observations). Root `snapshot/` and `snapshots/` are no longer defaulted — move files or pass explicit path args.
131
+ **Migration (T112–T114 hard-cut):** CLI verb is `state` only (no `snapshot` alias). Bare defaults are `config/views/` (views) and `state/` (observations). Prior `config/snapshot/` and root `snapshot/` / `snapshots/` are not fallbacks — move views to `config/views/` or pass explicit path args.
120
132
 
121
133
  ### Seed `endpoint:` symbols
122
134
 
@@ -290,7 +302,7 @@ The packaged full `config init` seed (under `config/`) is the only scaffold.
290
302
  ```sh
291
303
  gmake e2e # whole tier, about 20 minutes
292
304
  gmake e2e FILE=test_provision_lifecycle # apply/diff focus
293
- gmake e2e FILE=test_scenario_lifecycle # scenario + snapshot focus
305
+ gmake e2e FILE=test_scenario_lifecycle # scenario + state focus
294
306
  ```
295
307
 
296
308
  ## License
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acumatica-cli"
3
- version = "0.13.1"
3
+ version = "0.15.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"
@@ -12,7 +12,7 @@ import click
12
12
  import httpx
13
13
  from click.shell_completion import get_completion_class
14
14
 
15
- from . import bootstrap, extract, firstlogin, output, run, seed, snapshot
15
+ from . import bootstrap, extract, firstlogin, output, run, seed, state
16
16
  from .client import AcumaticaClient
17
17
  from .config import (
18
18
  Instance,
@@ -419,7 +419,7 @@ def config_init(host: str | None, directory: Path | None) -> None:
419
419
  Templates ship with the package; every value is a placeholder or a
420
420
  verified example - no secrets. Single full seed under ``config/``
421
421
  (bootstrap/baseline/setup/master) + lifecycle ``scenario/`` +
422
- ``config/snapshot/`` + README; full company ``project.xml``
422
+ ``config/views/`` + README; full company ``project.xml``
423
423
  (Bootstrap/1.0.0). No ``--flavor`` (V28/T108). Existing files are
424
424
  never overwritten (reported as skipped). DIRECTORY defaults to the
425
425
  current directory and is created if absent. No git init, no gpg.
@@ -437,6 +437,7 @@ def config_init(host: str | None, directory: Path | None) -> None:
437
437
  output.data(" 4. acu apply config/")
438
438
  output.data(" 5. acu run scenario/")
439
439
  output.data(" 6. acu diff config/")
440
+ output.data(" 7. acu state")
440
441
 
441
442
 
442
443
  @config_group.command("show")
@@ -863,9 +864,9 @@ def run_cmd(inst: Instance, files: tuple[Path, ...], dry_run: bool) -> None:
863
864
  def _complete_only(
864
865
  _ctx: click.Context, _param: click.Parameter, incomplete: str
865
866
  ) -> list[str]:
866
- """--only value completion: entity names off the packaged manifest.
867
+ """--only value completion: entity names off the packaged seed catalog.
867
868
 
868
- Fires per keystroke, so it stays local-only (V23): the manifest is
869
+ Fires per keystroke, so it stays local-only (V23): the catalog is
869
870
  package data - never REST, never SSH, never a live instance.
870
871
  """
871
872
  return [
@@ -887,7 +888,7 @@ def _complete_only(
887
888
  "--only",
888
889
  multiple=True,
889
890
  shell_complete=_complete_only,
890
- help="Limit to matching manifest rows (entity name or file stem); repeatable",
891
+ help="Limit to matching catalog rows (entity name or file stem); repeatable",
891
892
  )
892
893
  @click.option("--force", is_flag=True, help="Overwrite existing files")
893
894
  @click.option(
@@ -901,16 +902,17 @@ def extract_cmd(
901
902
  force: bool,
902
903
  dry_run: bool,
903
904
  ) -> None:
904
- """Extract live tenant state into seed YAML files (the inverse of apply).
905
-
906
- Manifest-driven (the packaged extract manifest carries the verified
907
- entity set): each entity is read from the live tenant and written as a
908
- seed file under bootstrap/ or baseline/ that apply and diff consume
909
- unchanged. Existing files are skipped unless --force; an entity with
910
- no live records produces no file. A failing row is reported and the
911
- run continues to the next (a virgin tenant extracts whole). Exit 0
912
- when every row wrote or skipped clean, 1 when any row failed - drift
913
- detection stays with diff.
905
+ """Extract live tenant state into seed YAML under config/ (inverse of apply).
906
+
907
+ Catalog-driven (packaged ``seed_catalog.yaml`` is the verified entity
908
+ registry): each row is read from the live tenant and written under
909
+ ``config/{bootstrap,baseline,setup,master}/`` (hard-cut; never root
910
+ SEED_DIRS; no ``--layout``). Apply and diff consume those files
911
+ unchanged. Features synthesize to ``config/bootstrap/features.yaml``.
912
+ Existing files are skipped unless --force; an entity with no live
913
+ records produces no file. A failing row is reported and the run
914
+ continues (a virgin tenant extracts whole). Exit 0 when every row
915
+ wrote or skipped clean, 1 when any row failed - drift stays with diff.
914
916
  """
915
917
  assert_target_compatible(inst)
916
918
  with AcumaticaClient(inst) as client:
@@ -935,7 +937,7 @@ def _exit_on_drift(inst: Instance, drifts: list[str], files: int) -> None:
935
937
  output.success(f"no drift on {inst.tenant} ({inst.base_url}, {files} file(s))")
936
938
 
937
939
 
938
- @cli.command("snapshot")
940
+ @cli.command("state")
939
941
  @click.argument(
940
942
  "files", nargs=-1, required=False, type=click.Path(exists=True, path_type=Path)
941
943
  )
@@ -963,7 +965,7 @@ def _exit_on_drift(inst: Instance, drifts: list[str], files: int) -> None:
963
965
  help="Resolve views and validate sources without HTTP",
964
966
  )
965
967
  @pass_instance
966
- def snapshot_cmd(
968
+ def state_cmd(
967
969
  inst: Instance,
968
970
  files: tuple[Path, ...],
969
971
  out_dir: Path | None,
@@ -973,39 +975,36 @@ def snapshot_cmd(
973
975
  ) -> None:
974
976
  """Capture live derived state into committed observation files.
975
977
 
976
- FILES are snapshot view YAML files or directories. Omitted, they default
977
- to the data repo's config/snapshot/ directory (hard-cut; no root
978
- snapshot/ fallback). Default write target is state/ (--out; no
979
- snapshots/ fallback). Bare capture writes observations (change is
980
- fine). --diff compares live to disk without writing.
981
- --assert-unchanged is the warm-run idempotence gate (exit 2 when
982
- moved). Never writes seed trees or endpoint: symbols (V32). Exit 0
978
+ FILES are view YAML files or directories. Omitted, they default to the
979
+ data repo's config/views/ directory (hard-cut; no config/snapshot/
980
+ fallback). Default write target is state/ (--out). Bare capture writes
981
+ observations (change is fine). --diff compares live to disk without
982
+ writing. --assert-unchanged is the warm-run idempotence gate (exit 2
983
+ when moved). Never writes seed trees or endpoint: symbols (V32). Exit 0
983
984
  ok, 1 op fail, 2 only under --assert-unchanged when state moved.
984
985
  """
985
986
  assert_target_compatible(inst)
986
987
  if not files:
987
- default = data_root() / "config" / "snapshot"
988
+ default = data_root() / "config" / "views"
988
989
  if not default.is_dir():
989
- raise SystemExit(f"{default}: snapshot directory does not exist")
990
+ raise SystemExit(f"{default}: views directory does not exist")
990
991
  files = (Path(os.path.relpath(default)),)
991
- paths = snapshot.expand_view_files(files)
992
- views = [snapshot.load_view(path) for path in paths]
992
+ paths = state.expand_view_files(files)
993
+ views = [state.load_view(path) for path in paths]
993
994
  dest = out_dir if out_dir is not None else Path("state")
994
995
  if dry_run:
995
- code = snapshot.run_views(None, views, out_dir=dest, mode="dry")
996
+ code = state.run_views(None, views, out_dir=dest, mode="dry")
996
997
  else:
997
998
  mode = "assert" if assert_unchanged else "diff" if do_diff else "write"
998
999
  with AcumaticaClient(inst) as client:
999
- code = snapshot.run_views(client, views, out_dir=dest, mode=mode)
1000
+ code = state.run_views(client, views, out_dir=dest, mode=mode)
1000
1001
  if code:
1001
1002
  raise SystemExit(code)
1002
1003
  if dry_run:
1003
1004
  return
1004
1005
  if assert_unchanged:
1005
- output.success(f"{len(views)} snapshot(s) unchanged on {inst.tenant}")
1006
+ output.success(f"{len(views)} view(s) unchanged on {inst.tenant}")
1006
1007
  elif do_diff:
1007
- output.success(f"{len(views)} snapshot(s) compared on {inst.tenant}")
1008
+ output.success(f"{len(views)} view(s) compared on {inst.tenant}")
1008
1009
  else:
1009
- output.success(
1010
- f"{len(views)} snapshot(s) written under {dest} on {inst.tenant}"
1011
- )
1010
+ output.success(f"{len(views)} view(s) written under {dest} on {inst.tenant}")
@@ -539,7 +539,7 @@ class AcumaticaClient:
539
539
  self._http.post("/CustomizationApi/publishEnd", json={})
540
540
  ).json()
541
541
 
542
- # -- OData Generic Inquiry (snapshot gi: source; V33) --
542
+ # -- OData Generic Inquiry (state gi: source; V33) --
543
543
 
544
544
  def _odata_gi_root(self) -> str:
545
545
  """24R2+ OData GI service root ``/t/<tenant>/api/odata/gi`` (V33)."""
@@ -61,10 +61,6 @@ INIT_TEMPLATES = (
61
61
  "config/baseline/60-ledger-company.yaml",
62
62
  ),
63
63
  ("config/baseline/90-uoms.yaml", "config/baseline/90-uoms.yaml"),
64
- (
65
- "config/baseline/91-company-packaging.yaml",
66
- "config/baseline/91-company-packaging.yaml",
67
- ),
68
64
  ("config/setup/10-financial-year.yaml", "config/setup/10-financial-year.yaml"),
69
65
  ("config/setup/20-master-calendar.yaml", "config/setup/20-master-calendar.yaml"),
70
66
  ("config/setup/30-open-periods.yaml", "config/setup/30-open-periods.yaml"),
@@ -118,8 +114,8 @@ INIT_TEMPLATES = (
118
114
  "config/master/85-kit-specifications.yaml",
119
115
  ),
120
116
  (
121
- "config/snapshot/10-trial-balance.yaml",
122
- "config/snapshot/10-trial-balance.yaml",
117
+ "config/views/10-trial-balance.yaml",
118
+ "config/views/10-trial-balance.yaml",
123
119
  ),
124
120
  ("scenario/10-seed-capital.yaml", "scenario/10-seed-capital.yaml"),
125
121
  ("scenario/20-buy.yaml", "scenario/20-buy.yaml"),