Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a0fe047
fold-in §V.58(+) and §T.251(+): github-issue-44
kborovik Sep 9, 2026
76c0553
T251: bootstrap contract NumberingSequence map Header NewSymbol; vers…
kborovik Sep 9, 2026
1fdb313
T252: seed_catalog include NewSymbol; package seed NewSymbol: <NEW>
kborovik Sep 9, 2026
9ab6a11
T253: offline tests for NumberingSequence NewSymbol insert
kborovik Sep 9, 2026
5eb3ace
T254: live e2e insert NumberingID with NewSymbol; re-apply BATCH
kborovik Sep 9, 2026
0fff15b
T255: docs and CHANGELOG for NumberingSequence NewSymbol insert
kborovik Sep 9, 2026
9f97721
review: lock NewSymbol mapped GET; drop diff ignore
kborovik Sep 9, 2026
fce5bc8
fix(extract): project AcctCD on Company GET during open-periods synth
kborovik Sep 9, 2026
4864e8f
amend §C.(+) and §T.256(+): e2e 3 pipeline files
kborovik Sep 9, 2026
7277bc5
T256: e2e 3 pipeline files (provision, scenario, extract)
kborovik Sep 9, 2026
003d1a0
amend §C.(+) and §V.2(+) and §T.257(+): CLI test seed at repo root
kborovik Sep 10, 2026
e30019a
T257: copy sibling GitOps config/{bootstrap,baseline,setup,master,vie…
kborovik Sep 10, 2026
e8ce9cf
T258: AGENTS.md + README: ACUCLI soak from this checkout --tenant ACU…
kborovik Sep 10, 2026
918c7fd
T259: offline test: repo-root seed trees present; no config/qms/; e2e…
kborovik Sep 10, 2026
18e8c10
review: point e2e decrypt at repo-root .env; drop stale NewSymbol GET…
kborovik Sep 10, 2026
4438305
chore(env): update encrypted environment variables
kborovik Sep 10, 2026
b944a8e
amend §V.58(+): mapped GET returns NewSymbol; GET-omit is not-returne…
kborovik Sep 10, 2026
4eff9b7
docs: remove Descr requirement for NumberingID inserts in demo-seed
kborovik Sep 10, 2026
cebc50b
amend §C.(+) §I.(+) §V.12(+) §V.42(+) §V.49(+) §T.260(+): drop docs/ …
kborovik Sep 10, 2026
16a26ac
T260: drop docs/ tree; sweep remaining docs/ cites
kborovik Sep 10, 2026
13abbb9
T261: sweep host + retired-sibling literals
kborovik Sep 10, 2026
dcad704
T262: extras-hook V49 drop docs/*.md glob
kborovik Sep 10, 2026
17b43cd
review: soak numbering NewSymbol; drop stale project.xml fallback
kborovik Sep 10, 2026
1ef891f
condense SPEC.md: prongs {3, 6} (~19.5k → ~15.6k tokens)
kborovik Sep 10, 2026
d276ffc
test(e2e): isolate harness from ambient ACU_* and support role skip
kborovik Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .env.gpg
Binary file not shown.
113 changes: 112 additions & 1 deletion .spec/check-extras.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ for granular /sdd:check runs.
## §V.49 — md-prose-density (mechanized §T.203)

- cmd: `.spec/scripts/check-md-prose` (also via extras-hook above)
- scope: `README.md`, `docs/*.md`, `src/acumatica_cli/templates/**/README.md`
- scope: `README.md`, `src/acumatica_cli/templates/**/README.md`
- prose paragraph = blank-line block; list / table / blockquote / fence / heading exempt
- surviving match (`V49|VIOLATE|file:line: N sentences`, exit 1) → bail: `prose paragraph ≥3 sentences in human-facing Markdown per §V.49 — split the paragraph`

Expand Down Expand Up @@ -266,3 +266,114 @@ for granular /sdd:check runs.
- `config/views` / `acu state` ! expand token (params pinned literals)
- unknown `${…}` hard fail
- no ERP business-date probe; no `--as-of`/`--period` CLI v1 (gh #28)

## §V.25 — seed-file key identity (extracted from SPEC.md §V.25)

- extract dup key tuple → row failure per V24, file never emitted
- `load_baseline` dup key tuple → hard error naming entity + first dup tuple
- catches hand-authored files (§B.14 workaround class; closes §B.21)

## §V.30 — seed-layout dual (extracted from SPEC.md §V.30)

- bare apply/diff defaults prefer data-repo `config/` SEED_DIRS when any child present, else root SEED_DIRS
- never merge dual trees; explicit path args still honored
- bootstrap features dual-resolve `config/bootstrap/` then `bootstrap/` (gh #19)
- `project.xml` ! dual-resolve (package SoT per V2/V21)
- extract emit hard-cut `config/` SEED_DIRS only (no root emit, no `--layout`)

## §V.36 — seed authority (extracted from SPEC.md §V.36)

- v1 writers into `config/` = REST extract + human only
- REST extract success for entity → seed/entity always wins vs inventory/table
- field/key conflict → `findings/` never silent overwrite seed
- post-v1 SnapshotMap promote only on REST gap (not prefer-artifact flag)
- unmapped tables never enter `config/`

## §V.37 — tenant-snapshot artifact (extracted from SPEC.md §V.37)

- accept SM203520 XML ZIP (`manifest.xml` + `*.xml`) or `ac.exe export xml` table-XML folder
- both normalize one IR → inventory/
- binary `.adb` fail-closed named error
- never matrix `erp` match
- format tag "XML dump" ? for ingress only — never identity side name (table owns identity)

## §V.40 — numbering-runtime (extracted from SPEC.md §V.40)

- seed bounds + NewSymbol fields: `NumberingID`, `StartNbr`, `EndNbr`, `WarnNbr`, `NbrStep` + StartDate? if screen requires
- NewSymbol insert-required per numbering-new-symbol invariant
- `LastNbr` (+ advanced counter) = runtime state — extract strips; diff ignores; apply never requires
- ! reset live counters every apply (gh #25)

## §V.44 — pin-overlay (extracted from SPEC.md §V.44)

- optional Default-half overlays under `overlays/default-<half>/` keyed by resolved `api_version` (flag or `ACU_API_VERSION` or code default)
- config init scaffolds layout + known rewrites
- bare apply/diff/run auto-compose pin overlay when path args omitted (scenario basenames replace; config SEED_DIRS append)
- explicit paths disable auto
- never `matrix.yaml`; never `--cell`
- CLI never requires long-running product branches
- never commit multi-version OpenAPI trees as SoT (`acu schema` live dump/gitignored)
- full seed version matrices live in data repos not CLI (extends V27)

## §V.48 — agent-help (extracted from SPEC.md §V.48)

- root `acu --help` ! MENTAL MODEL (data-repo layout, REST vs SSH planes, sole writer=`apply`, drift=`diff` exit 2, txns=`run`) + TYPICAL WORKFLOW (SSH box) + HOSTED path + CONFIG RESOLUTION + COMMAND MAP by intent + EXIT CODES + DEFAULT PATHS
- LLM agents learn tool from `--help` alone (README points agents @ root help)
- every subcommand help ! prerequisites + concrete examples + exit codes + related cmds
- accepts `-h` + `--help`
- help layout wide (`max_content_width` ≥ 100)

## §V.50 — segmented-key-seed (extracted from SPEC.md §V.50)

- SegmentedKey seed ! update existing `DimensionID` only (never insert new)
- package seeds `INVENTORY` + `BIZACCT` one alphanumeric segment `Length` 30 (DAC max)
- `ACCOUNT` + `INSITE` stay `Length` 10 (`SiteCD` NVarChar(10))
- never shrink `Length` after data exists
- key-URL GET after PUT ! return `SegmentID` + `Length`
- PUT `Length` ! persist so live InventoryID mask accepts 30
- silent HTTP 200 w/ omitted detail fields = mapping miss (closes §B.28) (gh #30)

## §V.52 — company-qty-precision (extracted from SPEC.md §V.52)

- Company CS101500 maps `DecPlQty` (`ShortValue`) + `WeightUOM`/`VolumeUOM` to view `commonsetup` (aspx DataMember; graph `Commonsetup`)
- unmapped PUT 200 ignores (B28 class)
- GET ! return mapped fields so extract/diff round-trip
- package seed `DecPlQty: 3` (kit BOM milligram-scale KG; DAC default 2)
- DistributionModule on → PUT ! send WeightUOM+VolumeUOM (`CommonSetup_RowPersisting`) even when extract strips GET-omit siblings (B26 StockItem class)
- contract shape change ! version bump (V21) (closes §B.30) (gh #34)

## §V.53 — user-role-membership (extracted from SPEC.md §V.53)

- SM201010 User.Roles and SM201005 Role.Users contract-detail PUT never write UsersInRoles (silent 200) on AllowedRoles, RolesByUser, RoleList, and UsersByRole
- persist path ! mapped contract detail
- prove via SQL UsersInRoles (session CompanyID)
- GET/diff membership per membership-diff-empty invariant
- UserRole ! Selected; never AllowedRoles (EPLoginTypeAllowsRole)
- closes §B.31, §B.32, §B.33 (gh #35)

## §V.54 — unwrap-row-id (extracted from SPEC.md §V.54)

- unwrap keeps `id` + `delete` on records and detail rows that also have value fields (wrap already leaves them bare)
- files-style descriptors that unwrap to only `id` stay elided
- GET `$expand` detail `id` ! round-trip onto later PUT so contract updates existing line
- missing id → insert + 500 Components commit (KitAssembly StockComponents class)
- closes §B.34 (gh #38)

## §V.57 — membership-diff-empty (extracted from SPEC.md §V.57)

- mapped GET Role.Users / User.Roles ? empty while UsersInRoles has session-company rows
- diff ! flag Role.Users / User.Roles missing
- skip those details in diff or read live source that returns rows
- AssignUser apply + SQL-proven membership → `acu diff` exit 0 on Role.Users / User.Roles
- closes §B.37 (gh #39)

## §V.58 — numbering-new-symbol (extracted from SPEC.md §V.58)

- NumberingSequence insert ! Header NewSymbol (or UserNumbering if screen requires pair)
- Bootstrap contract maps it
- apply PUT new NumberingID w/ `NewSymbol: <NEW>` ! 422
- re-apply existing id succeeds (no NewSymbol mask 422 on update)
- NewSymbol seed; mapped GET returns it
- GET-omit → `not returned by endpoint` drift (mapping-miss per segmented-key-seed invariant; not write-only-get-omit ignore)
- LastNbr strip per numbering-runtime invariant
- closes §B.38 (gh #44)
5 changes: 1 addition & 4 deletions .spec/scripts/check-md-prose
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""check-md-prose - V49 human-facing Markdown paragraph density.

Scope: README.md, docs/*.md, packaged template README.md under
Scope: README.md, packaged template README.md under
src/acumatica_cli/templates/. A prose paragraph (blank-line block) may
have at most 2 sentences. List items, table rows, blockquotes, fenced
code, and headings are exempt. SPEC.md / CHANGELOG / .spec stay out.
Expand Down Expand Up @@ -31,9 +31,6 @@ def scoped_files(root: Path) -> list[Path]:
readme = root / "README.md"
if readme.is_file():
files.append(readme)
docs = root / "docs"
if docs.is_dir():
files.extend(sorted(p for p in docs.glob("*.md") if p.is_file()))
templates = root / "src" / "acumatica_cli" / "templates"
if templates.is_dir():
files.extend(sorted(templates.rglob("README.md")))
Expand Down
46 changes: 21 additions & 25 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
# AGENTS.md

This repository is the `acu` CLI (`acumatica-cli`). It has no GitOps seed.
Do not `acu apply` / `diff` / `run` from here.
This repository is the `acu` CLI (`acumatica-cli`) plus the CLI test seed
(`config/` SEED_DIRS + `config/views/` + `scenario/` at repo root).

Live CLI tests use tenant login `ACUCLI` and sibling GitOps repo
`~/github/acu-gitops-qms` as the YAML source.

Never print `.env` secrets.
Product GitOps (QMS) stays in the sibling data repo. Never print `.env` secrets.

## Live test target

| | |
| --- | --- |
| Tenant login | `ACUCLI` (`ACU_TENANT=ACUCLI`) |
| GitOps source | `~/github/acu-gitops-qms` |
| CLI test seed | this checkout `config/` + `scenario/` |
| Instance | this repo's `.env` (`ACU_BASE_URL`) |

The GitOps repo `.env` names tenant `CNBN`. That is the QMS product tenant.
The sibling GitOps `.env` names tenant `CNBN`. That is the QMS product tenant.
Do not apply, delete, or rebuild `CNBN` while testing this CLI.
Do not change `~/github/acu-gitops-qms/.env`.
Do not run `gmake rebuild` there (it uses `.env` `ACU_TENANT`).
Do not `cd` the sibling for CLI soak.

Pass `--tenant ACUCLI` on every live command (flag wins over the GitOps `.env`).
Pass `--tenant ACUCLI` on every live command (flag wins over `.env` `ACU_TENANT`).

## Local CLI

Expand All @@ -38,30 +36,28 @@ uv run acu --version # 0.x.y+dev (<this checkout>)

Offline unit tests stay here: `gmake check`.
`gmake e2e` is a separate path: scratch tenants from packaged `config init` templates.
It is not the GitOps soak.
It is not the CLI test-seed soak.

## GitOps soak (from the data repo)
## CLI test-seed soak (this checkout)

Run from the GitOps tree so cwd walk-up finds `config/` and `.env`.
Set PATH to this CLI checkout's `.venv/bin` first, not the GitOps tree.
Run from this tree so cwd walk-up finds `config/` and `.env`.
Use this checkout's `.venv/bin/acu`.

```sh
PATH="$HOME/github/acumatica-cli/.venv/bin:$PATH"
cd ~/github/acu-gitops-qms

acu --tenant ACUCLI config check
acu --tenant ACUCLI tenant list
gmake install
uv run acu --tenant ACUCLI config check
uv run acu --tenant ACUCLI tenant list

# cold rebuild of the CLI test tenant only
acu --tenant ACUCLI tenant delete --login ACUCLI --yes
acu --tenant ACUCLI tenant create --login ACUCLI
acu --tenant ACUCLI apply
acu --tenant ACUCLI run
acu --tenant ACUCLI diff
acu --tenant ACUCLI state
uv run acu --tenant ACUCLI tenant delete --login ACUCLI --yes
uv run acu --tenant ACUCLI tenant create --login ACUCLI
uv run acu --tenant ACUCLI apply
uv run acu --tenant ACUCLI run
uv run acu --tenant ACUCLI diff
uv run acu --tenant ACUCLI state
```

`config/qms/` needs Lab5.QMS published. Skip it unless the change under test is QMS apply.
This seed has no `config/qms/`. QMS apply stays in the sibling GitOps repo.

## Related repos

Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@

## Unreleased

### Removed

- **docs/ tree:** dropped. Live verify is `.env` plus `acu schema`. README
and `acu --help` stay the human-facing surface.

### Fixed

- **NumberingSequence NewSymbol insert (gh #44):** Bootstrap `1.11.0` maps
Header `NewSymbol`. Insert of a new NumberingID with `NewSymbol: <NEW>`
no longer 422s. Re-apply of existing sequences still succeeds. `LastNbr`
stays runtime (extract strips, diff ignores, apply never PUTs).
`NewSymbol` is seed; mapped GET returns it. GET-omit is `not returned by endpoint`
drift.
- **extract open-periods Company GET:** `30-open-periods.yaml` synthesis
`$select`s `AcctCD` so the Company list GET does not 500 on
`DecPlQty`/`WeightUOM`/`VolumeUOM` (CS101500 `commonsetup` BQL delegate).
Unprojected list GET left extract exit 1 and a replayed tenant with
inactive periods.

## [v0.33.1] - 2026-09-08

### Fixed
Expand Down
38 changes: 26 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ acu --tenant DEV state # capture state/ trial-balance
```

Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
See [docs/demo-seed.md](docs/demo-seed.md) for the entity map, once-guard, apply-order notes, NumberingSequence vs prefs `*NumberingID`, curated *Preferences field depth (V41), and Role/User + password seed rules.

**Hosted Acumatica (no SSH):** the tenant already exists; set a blank `ACU_SSH=` in `.env`.
The scaffold omits the key — without it, acu defaults to `Administrator@<ACU_BASE_URL host>` for SSH boxes.
Expand Down Expand Up @@ -122,7 +121,7 @@ When you omit FILES:
- `state` defaults to `config/views/`; writes go to `state/` (`--out`).

Scenario YAML may use `${current_period}` (host-local `MMyyyy`) on steps, expect params, and `once.present` params.
`config/views` and `state` keep Period pinned — see [docs/demo-seed.md](docs/demo-seed.md#period-token-current_period-vs-pinned-views).
`config/views` and `state` keep Period pinned.

`survey extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).

Expand All @@ -135,8 +134,6 @@ It never writes seed.
Optional `snapshot_map.yaml` (data-repo root, or package defaults) maps DAC tables to catalog entities.
It normalizes the join: pad-trim, key/field aliases, Account/Sub FK CD resolve, enum label to code.

See [docs/demo-seed.md](docs/demo-seed.md).

`acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.

Run `acu --help` for the full mental model (workflow, planes, exit codes, command map).
Expand All @@ -160,8 +157,6 @@ Do not confuse them with each other or with `state`:
`inventory/` and `findings/` are engagement outputs: not SEED_DIRS, never loaded by `apply`/`diff`, not scaffolded by `config init`.
Binary `.adb` snapshots are rejected (XML only).

See [docs/ac-exe.md](docs/ac-exe.md) for export / SM203520 notes and [docs/demo-seed.md](docs/demo-seed.md) for the extract/state/inventory map.

## The data repo

Your configuration lives in its own git repo.
Expand Down Expand Up @@ -194,7 +189,6 @@ Seed YAML is state: `apply` upserts it, `diff` proves it.
`acu survey 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.
Expand Down Expand Up @@ -250,7 +244,7 @@ Secrets, REST where, and the Default contract pin live in one `.env` file (`ACU_
Leftover `matrix.yaml` is ignored and never loaded.

```sh
ACU_BASE_URL=http://acu-dev1.vm.internal/AcumaticaERP # REST where (required)
ACU_BASE_URL=http://erp.example.com/AcumaticaERP # REST where (required)
ACU_API_VERSION=25.200.001 # Default contract half
ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
# ACU_SSH omitted → defaults to Administrator@ + resolved base_url host
Expand Down Expand Up @@ -373,16 +367,36 @@ GitHub Actions on tag `v*` re-runs CI, builds sdist+wheel, publishes to PyPI via
Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `ACU_TENANT`, `ACU_PASSWORD` (and optional `ACU_SSH`; omitted defaults to `Administrator@` + base-url host).
`gmake e2e` refuses to start without it.

The tier is self-contained.
The tier is three pipeline files: provision (apply/diff), scenario (run/state), and extract round-trip.
Per-bug contract probes fold onto those tenants.

Each run scaffolds a synthetic single-org company from the packaged `acu config init` templates into a temporary directory, copies the real `.env` into it, and runs the installed `acu` binary from there — no data repo, no pre-existing fixtures on the instance.

Scratch tenants (`E2E`, `E2EA`, `E2EB`, `E2ESCEN`) are created on the way in and always deleted on the way out, so nothing persists.
The packaged full `config init` seed (under `config/`) is the only scaffold.

```sh
gmake e2e # whole tier, about 20 minutes
gmake e2e FILE=test_provision_lifecycle # apply/diff focus
gmake e2e FILE=test_scenario_lifecycle # scenario + state focus
gmake e2e # whole tier, three files
gmake e2e FILE=test_provision_lifecycle # apply/diff + folded probes
gmake e2e FILE=test_scenario_lifecycle # scenario + state + kit alloc
gmake e2e FILE=test_extract_roundtrip # extract inverse
```

### CLI test-seed soak

This checkout carries a CLI test seed at repo-root `config/` and `scenario/` (no `config/qms/`).
Soak the live CLI tenant from here with `--tenant ACUCLI`.

Never use tenant `CNBN`. Never `cd` the sibling GitOps repo for CLI soak.

`gmake e2e` still scaffolds from packaged `config init` templates into a tmp dir.
That path is not the soak.

```sh
uv run acu --tenant ACUCLI apply
uv run acu --tenant ACUCLI run
uv run acu --tenant ACUCLI diff
uv run acu --tenant ACUCLI state
```

## License
Expand Down
Loading
Loading