acumatica-cli 0.10.3__tar.gz → 0.12.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 (72) hide show
  1. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/PKG-INFO +47 -18
  2. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/README.md +46 -17
  3. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/pyproject.toml +1 -1
  4. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/src/acumatica_cli/bootstrap.py +33 -22
  5. acumatica_cli-0.12.0/src/acumatica_cli/bootstrap_project.xml +789 -0
  6. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/src/acumatica_cli/cli.py +80 -28
  7. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/src/acumatica_cli/client.py +21 -2
  8. acumatica_cli-0.12.0/src/acumatica_cli/config.py +370 -0
  9. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/src/acumatica_cli/extract.py +4 -4
  10. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/src/acumatica_cli/extract_manifest.yaml +1 -1
  11. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/src/acumatica_cli/run.py +164 -30
  12. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/src/acumatica_cli/seed.py +5 -4
  13. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/README.md +59 -0
  14. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/baseline/20-accounts.yaml +238 -0
  15. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/baseline/60-ledger-company.yaml +27 -0
  16. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/baseline/90-uoms.yaml +17 -0
  17. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/bootstrap/company.yaml +15 -0
  18. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/bootstrap/features.yaml +26 -0
  19. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/10-reason-codes.yaml +49 -0
  20. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/20-in-preferences.yaml +34 -0
  21. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/30-availability-rules.yaml +16 -0
  22. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/40-posting-classes.yaml +44 -0
  23. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/50-warehouse.yaml +23 -0
  24. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/51-warehouse-locations.yaml +26 -0
  25. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/52-warehouse-defaults.yaml +13 -0
  26. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/53-tax-categories.yaml +14 -0
  27. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/54-item-classes.yaml +43 -0
  28. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/56-so-preferences.yaml +21 -0
  29. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/57-po-preferences.yaml +21 -0
  30. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/58-order-types.yaml +21 -0
  31. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/60-ar-preferences.yaml +15 -0
  32. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/61-ap-preferences.yaml +14 -0
  33. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/62-ca-preferences.yaml +18 -0
  34. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/63-cash-account.yaml +23 -0
  35. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/64-payment-methods.yaml +27 -0
  36. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/65-statement-cycles.yaml +23 -0
  37. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/70-vendor-classes.yaml +38 -0
  38. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/71-customer-classes.yaml +28 -0
  39. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/75-vendors.yaml +73 -0
  40. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/76-customers.yaml +54 -0
  41. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/80-stock-items-parts.yaml +63 -0
  42. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/82-stock-items-kits.yaml +37 -0
  43. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/master/85-kit-specifications.yaml +94 -0
  44. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/scenario/10-seed-capital.yaml +46 -0
  45. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/scenario/20-buy-gateways.yaml +112 -0
  46. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/scenario/30-build.yaml +5 -0
  47. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/scenario/40-sell.yaml +264 -0
  48. acumatica_cli-0.12.0/src/acumatica_cli/templates/distribution/setup/30-open-periods.yaml +33 -0
  49. acumatica_cli-0.10.3/src/acumatica_cli/bootstrap_project.xml +0 -238
  50. acumatica_cli-0.10.3/src/acumatica_cli/config.py +0 -198
  51. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/src/acumatica_cli/__init__.py +0 -0
  52. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/src/acumatica_cli/bootstrap_plugin.cs +0 -0
  53. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/src/acumatica_cli/firstlogin.py +0 -0
  54. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/src/acumatica_cli/models.py +0 -0
  55. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/src/acumatica_cli/output.py +0 -0
  56. {acumatica_cli-0.10.3 → acumatica_cli-0.12.0}/src/acumatica_cli/target.py +0 -0
  57. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/baseline/10-subaccounts.yaml +0 -0
  58. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/baseline/20-accounts.yaml +0 -0
  59. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/baseline/40-ledger.yaml +0 -0
  60. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/baseline/50-gl-preferences.yaml +0 -0
  61. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/baseline/60-ledger-company.yaml +0 -0
  62. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/baseline/90-uoms.yaml +0 -0
  63. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/bootstrap/company.yaml +0 -0
  64. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/bootstrap/credit-terms.yaml +0 -0
  65. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/bootstrap/features.yaml +0 -0
  66. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/env +0 -0
  67. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/gitignore +0 -0
  68. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/setup/10-financial-year.yaml +0 -0
  69. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/setup/20-master-calendar.yaml +0 -0
  70. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/setup/30-open-periods.yaml +0 -0
  71. {acumatica_cli-0.10.3/src/acumatica_cli/templates → acumatica_cli-0.12.0/src/acumatica_cli/templates/finance}/target +0 -0
  72. {acumatica_cli-0.10.3 → acumatica_cli-0.12.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.10.3
3
+ Version: 0.12.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>
@@ -16,7 +16,7 @@ Project-URL: Homepage, https://lab5.ca
16
16
  Project-URL: Repository, https://github.com/kborovik/acumatica-cli
17
17
  Description-Content-Type: text/markdown
18
18
 
19
- # Acumatica GitOps (Config-as-Code)
19
+ # Acumatica ERP - GitOps CLI
20
20
 
21
21
  **`acu`** configures Acumatica ERP from YAML files in a git repo (GitOps).
22
22
 
@@ -43,10 +43,23 @@ cd my-erp # edit .env: set ACU_PASSWORD
43
43
 
44
44
  acu config check # read-only preflight (incl. target.yaml)
45
45
  acu tenant create --id 3 --login DEV # create the tenant + bootstrap it (needs SSH)
46
- acu --tenant DEV apply # seed bootstrap/, baseline/, setup/
46
+ acu --tenant DEV apply # seed bootstrap/, baseline/, setup/ (, master/)
47
47
  acu --tenant DEV diff # prove zero drift (exit 2 on drift)
48
48
  ```
49
49
 
50
+ **Distribution demo seed** (inventory, warehouse, items, vendors/customers, lifecycle scenarios):
51
+
52
+ ```sh
53
+ acu config init --flavor distribution --host erp.example.com my-dist
54
+ cd my-dist # edit .env; start from an empty tenant
55
+ acu config check && acu bootstrap \
56
+ && acu apply config/ && acu run scenario/ && acu diff config/
57
+ # warm re-run: capital once-skips; Owner Capital stays 50000 (not 100000)
58
+ acu run scenario/
59
+ ```
60
+
61
+ See [docs/distribution.md](docs/distribution.md) for the entity map, once-guard, and apply-order notes.
62
+
50
63
  **Hosted Acumatica (no SSH):** the tenant already exists; leave `ACU_SSH` blank.
51
64
 
52
65
  ```sh
@@ -81,36 +94,42 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
81
94
  ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
82
95
  │
83
96
  └── config configuration ops
84
- ├── init [--host HOST] [DIR] scaffold a data repo (.env, target.yaml, example YAML)
97
+ ├── init [--host HOST] [--flavor distribution] [DIR]
98
+ │ scaffold a data repo (.env, target.yaml, example YAML)
85
99
  ├── show print the resolved config as a complete .env
86
100
  └── check [--strict] preflight: discovery, secrets, target, REST, endpoints, SSH
87
101
  ```
88
102
 
89
- `apply` and `diff` called without FILES default to the scaffolded directories, in order: `bootstrap/`, then `baseline/`, then `setup/`.
90
- `run` called without FILES defaults to `scenario/`.
103
+ `apply` and `diff` without FILES prefer `config/<name>/` when any seed child exists under `config/`; otherwise root `bootstrap/`, `baseline/`, `setup/`, then `master/` when present.
104
+ A path like `config/` expands nested seed dirs in that fixed order.
105
+ `run` without FILES defaults to `scenario/`.
91
106
  `acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
92
107
  Run `acu <command> --help` for details on any command.
93
108
 
94
109
  ## The data repo
95
110
 
96
- Your configuration lives in its own git repo. `acu config init` scaffolds everything except `scenario/`, which you author by hand:
111
+ Your configuration lives in its own git repo.
112
+ `acu config init` scaffolds finance-minimal seeds at the **root** (Bootstrap `project.xml` at `Bootstrap/1.0.0`, no `master/`, no scenario).
113
+ `acu config init --flavor distribution` scaffolds the full demo under `config/` (same contract identity, expanded COA, masters) plus lifecycle `scenario/` and README.
97
114
 
98
- | Path | What it holds |
99
- | ------------- | -------------------------------------------------------------------------- |
100
- | `bootstrap/` | what makes a virgin tenant configurable: features, company, credit terms |
101
- | `baseline/` | reference data: subaccounts, chart of accounts, ledger, units of measure |
102
- | `setup/` | one-time actions: financial year, master calendar, open periods |
103
- | `scenario/` | transaction scenarios for `acu run`: purchase, build, sell flows |
104
- | `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
105
- | `.env` | where to apply and who signs in, every key an `ACU_*` variable |
115
+ | Path | What it holds |
116
+ | ---- | ------------- |
117
+ | `bootstrap/` / `config/bootstrap/` | virgin-tenant config: features, company, credit terms, `project.xml` |
118
+ | `baseline/` / `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs |
119
+ | `setup/` / `config/setup/` | one-time actions: financial year, master calendar, open periods |
120
+ | `config/master/` | distribution masters (prefs, warehouse, items, parties); flavor only |
121
+ | `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build stub, sell |
122
+ | `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
123
+ | `.env` | where to apply and who signs in, every key an `ACU_*` variable |
106
124
 
107
125
  Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-`, and so on) encode dependency order.
108
126
  The scaffolded `.gitignore` keeps `.env` out of git — store it encrypted (for example as `.env.gpg`) and decrypt once per clone.
109
127
  Commit `target.yaml` with the seeds so every clone knows the verified ERP line and Default API generation.
110
128
 
111
- Seed YAML in `bootstrap/`, `baseline/`, and `setup/` is state: `apply` upserts it, `diff` proves it.
129
+ Seed YAML is state: `apply` upserts it, `diff` proves it.
112
130
  Scenario YAML is different — it describes transactions that flow forward.
113
- `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 a scenario re-runs safely on a warm tenant.
131
+ `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.
132
+ `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).
114
133
 
115
134
  ### Seed `endpoint:` symbols
116
135
 
@@ -121,7 +140,7 @@ Dual-served entities (on both Bootstrap and Default) need an explicit `endpoint:
121
140
  | omitted | `Default/<ACU_API_VERSION>` for Default-only entities |
122
141
  | `bootstrap` | active `Bootstrap/<ver>` from `bootstrap/project.xml` or the packaged contract |
123
142
  | `default` | `Default/<ACU_API_VERSION>` — tracks the operator API version |
124
- | `Bootstrap/1.9.0` or `Default/25.200.001` | literal pin (ignores `ACU_API_VERSION` for Default) |
143
+ | `Bootstrap/1.0.0` or `Default/25.200.001` | literal pin (ignores `ACU_API_VERSION` for Default) |
125
144
 
126
145
  Prefer symbolic `default` over a pinned `Default/25.200.001` so the seed tree travels with the configured API generation.
127
146
 
@@ -258,6 +277,16 @@ gmake check # offline gate: ruff, basedpyright strict, pytest
258
277
  The default test suite is fully offline.
259
278
  REST is faked with `httpx.MockTransport`, SSH with a monkeypatched `subprocess.run` — no live instance is needed.
260
279
  `gmake check` must pass before every commit.
280
+ GitHub Actions runs the same gate on every push and pull request to `main`.
281
+
282
+ ### Release
283
+
284
+ ```sh
285
+ gmake release patch # or minor | major
286
+ ```
287
+
288
+ Local release runs `gmake check`, bumps the version, commits, tags `v<version>`, and pushes.
289
+ GitHub Actions re-runs the check on the tag, then publishes the GitHub release and PyPI package only if that check passes.
261
290
 
262
291
  ### Live end-to-end tier
263
292
 
@@ -1,4 +1,4 @@
1
- # Acumatica GitOps (Config-as-Code)
1
+ # Acumatica ERP - GitOps CLI
2
2
 
3
3
  **`acu`** configures Acumatica ERP from YAML files in a git repo (GitOps).
4
4
 
@@ -25,10 +25,23 @@ cd my-erp # edit .env: set ACU_PASSWORD
25
25
 
26
26
  acu config check # read-only preflight (incl. target.yaml)
27
27
  acu tenant create --id 3 --login DEV # create the tenant + bootstrap it (needs SSH)
28
- acu --tenant DEV apply # seed bootstrap/, baseline/, setup/
28
+ acu --tenant DEV apply # seed bootstrap/, baseline/, setup/ (, master/)
29
29
  acu --tenant DEV diff # prove zero drift (exit 2 on drift)
30
30
  ```
31
31
 
32
+ **Distribution demo seed** (inventory, warehouse, items, vendors/customers, lifecycle scenarios):
33
+
34
+ ```sh
35
+ acu config init --flavor distribution --host erp.example.com my-dist
36
+ cd my-dist # edit .env; start from an empty tenant
37
+ acu config check && acu bootstrap \
38
+ && acu apply config/ && acu run scenario/ && acu diff config/
39
+ # warm re-run: capital once-skips; Owner Capital stays 50000 (not 100000)
40
+ acu run scenario/
41
+ ```
42
+
43
+ See [docs/distribution.md](docs/distribution.md) for the entity map, once-guard, and apply-order notes.
44
+
32
45
  **Hosted Acumatica (no SSH):** the tenant already exists; leave `ACU_SSH` blank.
33
46
 
34
47
  ```sh
@@ -63,36 +76,42 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
63
76
  ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
64
77
  │
65
78
  └── config configuration ops
66
- ├── init [--host HOST] [DIR] scaffold a data repo (.env, target.yaml, example YAML)
79
+ ├── init [--host HOST] [--flavor distribution] [DIR]
80
+ │ scaffold a data repo (.env, target.yaml, example YAML)
67
81
  ├── show print the resolved config as a complete .env
68
82
  └── check [--strict] preflight: discovery, secrets, target, REST, endpoints, SSH
69
83
  ```
70
84
 
71
- `apply` and `diff` called without FILES default to the scaffolded directories, in order: `bootstrap/`, then `baseline/`, then `setup/`.
72
- `run` called without FILES defaults to `scenario/`.
85
+ `apply` and `diff` without FILES prefer `config/<name>/` when any seed child exists under `config/`; otherwise root `bootstrap/`, `baseline/`, `setup/`, then `master/` when present.
86
+ A path like `config/` expands nested seed dirs in that fixed order.
87
+ `run` without FILES defaults to `scenario/`.
73
88
  `acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
74
89
  Run `acu <command> --help` for details on any command.
75
90
 
76
91
  ## The data repo
77
92
 
78
- Your configuration lives in its own git repo. `acu config init` scaffolds everything except `scenario/`, which you author by hand:
93
+ Your configuration lives in its own git repo.
94
+ `acu config init` scaffolds finance-minimal seeds at the **root** (Bootstrap `project.xml` at `Bootstrap/1.0.0`, no `master/`, no scenario).
95
+ `acu config init --flavor distribution` scaffolds the full demo under `config/` (same contract identity, expanded COA, masters) plus lifecycle `scenario/` and README.
79
96
 
80
- | Path | What it holds |
81
- | ------------- | -------------------------------------------------------------------------- |
82
- | `bootstrap/` | what makes a virgin tenant configurable: features, company, credit terms |
83
- | `baseline/` | reference data: subaccounts, chart of accounts, ledger, units of measure |
84
- | `setup/` | one-time actions: financial year, master calendar, open periods |
85
- | `scenario/` | transaction scenarios for `acu run`: purchase, build, sell flows |
86
- | `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
87
- | `.env` | where to apply and who signs in, every key an `ACU_*` variable |
97
+ | Path | What it holds |
98
+ | ---- | ------------- |
99
+ | `bootstrap/` / `config/bootstrap/` | virgin-tenant config: features, company, credit terms, `project.xml` |
100
+ | `baseline/` / `config/baseline/` | reference data: subaccounts, COA, ledger, UOMs |
101
+ | `setup/` / `config/setup/` | one-time actions: financial year, master calendar, open periods |
102
+ | `config/master/` | distribution masters (prefs, warehouse, items, parties); flavor only |
103
+ | `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build stub, sell |
104
+ | `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
105
+ | `.env` | where to apply and who signs in, every key an `ACU_*` variable |
88
106
 
89
107
  Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-`, and so on) encode dependency order.
90
108
  The scaffolded `.gitignore` keeps `.env` out of git — store it encrypted (for example as `.env.gpg`) and decrypt once per clone.
91
109
  Commit `target.yaml` with the seeds so every clone knows the verified ERP line and Default API generation.
92
110
 
93
- Seed YAML in `bootstrap/`, `baseline/`, and `setup/` is state: `apply` upserts it, `diff` proves it.
111
+ Seed YAML is state: `apply` upserts it, `diff` proves it.
94
112
  Scenario YAML is different — it describes transactions that flow forward.
95
- `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 a scenario re-runs safely on a warm tenant.
113
+ `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.
114
+ `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).
96
115
 
97
116
  ### Seed `endpoint:` symbols
98
117
 
@@ -103,7 +122,7 @@ Dual-served entities (on both Bootstrap and Default) need an explicit `endpoint:
103
122
  | omitted | `Default/<ACU_API_VERSION>` for Default-only entities |
104
123
  | `bootstrap` | active `Bootstrap/<ver>` from `bootstrap/project.xml` or the packaged contract |
105
124
  | `default` | `Default/<ACU_API_VERSION>` — tracks the operator API version |
106
- | `Bootstrap/1.9.0` or `Default/25.200.001` | literal pin (ignores `ACU_API_VERSION` for Default) |
125
+ | `Bootstrap/1.0.0` or `Default/25.200.001` | literal pin (ignores `ACU_API_VERSION` for Default) |
107
126
 
108
127
  Prefer symbolic `default` over a pinned `Default/25.200.001` so the seed tree travels with the configured API generation.
109
128
 
@@ -240,6 +259,16 @@ gmake check # offline gate: ruff, basedpyright strict, pytest
240
259
  The default test suite is fully offline.
241
260
  REST is faked with `httpx.MockTransport`, SSH with a monkeypatched `subprocess.run` — no live instance is needed.
242
261
  `gmake check` must pass before every commit.
262
+ GitHub Actions runs the same gate on every push and pull request to `main`.
263
+
264
+ ### Release
265
+
266
+ ```sh
267
+ gmake release patch # or minor | major
268
+ ```
269
+
270
+ Local release runs `gmake check`, bumps the version, commits, tags `v<version>`, and pushes.
271
+ GitHub Actions re-runs the check on the tag, then publishes the GitHub release and PyPI package only if that check passes.
243
272
 
244
273
  ### Live end-to-end tier
245
274
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acumatica-cli"
3
- version = "0.10.3"
3
+ version = "0.12.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"
@@ -8,10 +8,11 @@ virgin tenant, so bootstrap = publish a package whose CustomizationPlugin
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
- Contract ownership is hybrid (T69/V2): the data repo's
12
- ``bootstrap/project.xml`` is preferred when present (full company surface);
13
- else the packaged minimal ``bootstrap_project.xml`` covers config-init +
14
- the GL chain so PyPI-only and offline virgin paths still bootstrap.
11
+ Contract ownership is hybrid (T69/V2/T81/T85): the data repo's
12
+ ``config/bootstrap/project.xml`` is preferred, then root
13
+ ``bootstrap/project.xml``; else the packaged full company
14
+ ``bootstrap_project.xml`` (``Bootstrap/1.0.0``) so PyPI-only and offline
15
+ virgin paths still bootstrap the full surface.
15
16
 
16
17
  Customization publishes are tenant-scoped, so the package must be published
17
18
  per tenant; publish() is idempotent on content — the skip gate compares the
@@ -19,8 +20,9 @@ digest embedded in the published package's description against the package
19
20
  built now, and the plugin's UpdateDatabase is a keyed update on re-run.
20
21
 
21
22
  The feature set the plugin enables is data, not code (V2): load_features()
22
- reads the data repo's bootstrap/features.yaml (absent -> the built-in six)
23
- and package_zip() splices it into the plugin source at build time.
23
+ reads config/bootstrap/features.yaml then root bootstrap/features.yaml
24
+ (absent -> the built-in six) and package_zip() splices it into the plugin
25
+ source at build time.
24
26
  """
25
27
 
26
28
  import hashlib
@@ -43,11 +45,10 @@ PACKAGE_NAME = "AcuBootstrap"
43
45
  PLUGIN_CLASS = "AcuBootstrapPlugin"
44
46
  _ENDPOINT_NS = "{http://www.acumatica.com/entity/maintenance/5.31}"
45
47
 
46
- # Code default when the data repo carries no bootstrap/features.yaml
47
- # (SPEC I.data) — the minimum for company/branch + baseline seeding.
48
- # The set is injected into the plugin source at package build; it never
49
- # lives in bootstrap_plugin.cs (V2: feature flags are config, not tool
50
- # source — B6).
48
+ # Code default when the data repo carries no features.yaml (SPEC I.data)
49
+ # — the minimum for company/branch + baseline seeding. The set is injected
50
+ # into the plugin source at package build; it never lives in
51
+ # bootstrap_plugin.cs (V2: feature flags are config, not tool source — B6).
51
52
  DEFAULT_FEATURES = (
52
53
  "FinancialModule",
53
54
  "FinancialStandard",
@@ -59,8 +60,16 @@ DEFAULT_FEATURES = (
59
60
  FEATURES_SENTINEL = "/*ACU_FEATURES*/"
60
61
 
61
62
 
63
+ def _bootstrap_file(root: Path, name: str) -> Path | None:
64
+ """Resolve a bootstrap package input: config/bootstrap then root (V30)."""
65
+ for path in (root / "config" / "bootstrap" / name, root / "bootstrap" / name):
66
+ if path.is_file():
67
+ return path
68
+ return None
69
+
70
+
62
71
  def packaged_contract_xml() -> bytes:
63
- """The CLI-shipped minimal Bootstrap contract (config-init surface)."""
72
+ """The CLI-shipped full company Bootstrap contract (``Bootstrap/1.0.0``)."""
64
73
  return (resources.files("acumatica_cli") / "bootstrap_project.xml").read_bytes()
65
74
 
66
75
 
@@ -68,12 +77,13 @@ def load_contract_xml(root: Path | None = None) -> bytes:
68
77
  """Active contract bytes: data-repo override when present, else packaged.
69
78
 
70
79
  ``root`` is the data-repo discovery root (the dir holding ``.env``).
71
- Absent root or absent ``bootstrap/project.xml`` falls back to the
72
- packaged minimal contract (V2, same absence pattern as features.yaml).
80
+ Prefer ``config/bootstrap/project.xml``, then root ``bootstrap/project.xml``
81
+ (V30 dual resolve). Absent root or file falls back to the packaged full
82
+ company contract (V2/T81, same absence pattern as features.yaml).
73
83
  """
74
84
  if root is not None:
75
- path = root / "bootstrap" / "project.xml"
76
- if path.is_file():
85
+ path = _bootstrap_file(root, "project.xml")
86
+ if path is not None:
77
87
  return path.read_bytes()
78
88
  return packaged_contract_xml()
79
89
 
@@ -97,15 +107,16 @@ PACKAGE_DESCRIPTION = ET.fromstring(packaged_contract_xml()).get("description",
97
107
 
98
108
 
99
109
  def load_features(root: Path) -> list[str]:
100
- """The FeaturesSet property names from <root>/bootstrap/features.yaml.
110
+ """FeaturesSet property names from config/ then root bootstrap (V30).
101
111
 
112
+ Prefer ``config/bootstrap/features.yaml``, then ``bootstrap/features.yaml``.
102
113
  Absent file -> the built-in six (SPEC I.data). Names are validated as
103
114
  plausible property names here (they are spliced into C# string literals);
104
115
  whether each matches a real FeaturesSet property only the plugin can
105
116
  tell — it logs the strays at publish time (the silent-typo guard).
106
117
  """
107
- path = root / "bootstrap" / "features.yaml"
108
- if not path.is_file():
118
+ path = _bootstrap_file(root, "features.yaml")
119
+ if path is None:
109
120
  return list(DEFAULT_FEATURES)
110
121
  with open(path) as f:
111
122
  data = yaml.safe_load(f)
@@ -136,9 +147,9 @@ def package_zip(
136
147
  ``Enabled`` set at the ACU_FEATURES sentinel — the one point where the
137
148
  data repo's feature list enters the package (V2).
138
149
 
139
- Contract XML (V2/T69): ``contract`` when given; else
140
- ``bootstrap/project.xml`` under ``root`` when present; else the
141
- packaged minimal template.
150
+ Contract XML (V2/T69/T81/T85): ``contract`` when given; else
151
+ ``config/bootstrap/project.xml`` then ``bootstrap/project.xml`` under
152
+ ``root`` when present; else the packaged full company template.
142
153
  """
143
154
  pkg = resources.files("acumatica_cli")
144
155
  if contract is None: