acumatica-lint
Free, open-source linter for Acumatica customization projects. Catches publish-time failure modes — silent
NullReferenceExceptions, dropped DAC extensions, orphan SiteMap rows, GI catalog exclusions, schema mutations withoutIF NOT EXISTSguards — before they restart your production app pool.Want this run automatically on every PR, plus sandbox dry-run before prod, snapshot/rollback with integrity verification, Codex AI review, Slack telemetry, and deploy-window enforcement? → AcuOps
Install
pip install acumatica-lint
Python 3.10+. One dependency (pyyaml for the ISV co-publish allowlist parser). Works on macOS, Linux, and Windows.
Use
Point it at a project.xml:
acumatica-lint Customization/MyPackage/project.xml
With strict mode (additional warnings for best-practice violations):
acumatica-lint --strict Customization/MyPackage/project.xml
ISV authors — enforce a class-name prefix on all <Graph> declarations:
acumatica-lint --isv-prefix AO Customization/MyISV/project.xml
Debug-only DLL-source plugin scan (skips XML/Graph/semantic checks):
acumatica-lint --dll-source-only Customization/MyPackage/project.xml
Exit codes
0— passed (with or without warnings)1— failed (one or more hard-fail errors)2— usage error (bad CLI args)
What it catches
acumatica-lint encodes ~60 failure-mode rules from real Acumatica production incidents Studio B has resolved. A non-exhaustive list:
- DAC fields without matching
<Sql>ALTER TABLE — auto-column creation has been broken on cloud Acumatica since early 2026.[PXDBx]fields without a corresponding<Sql>ALTER will silently fail at publish. - Phantom DAC references in
<GITable Name>— silently aborts GI processing without an error in the publish log. - GI Creation Contract — every new OData-exposed GI requires
<RolesInGraph Rolename="*" Accessrights="4" ApplicationName="/" />AND a matching entry in the package'sCustomizationPluginallowlist. Miss either and the GI silently catalog-excludes, returning HTTP 404 from/OData/{tenant}/$metadata. - SiteMap orphans (
ParentID="00000000-...") — invisible inSM205010 (Access Rights by Role), ungrantable, OData 404. - Modern GI format requirements (24.208+) —
GITable.Namemust be fully-qualified DAC name;<GIWhere Condition>must use Acumatica's vocabulary ("E ","NE","GE", etc., NOT"EQ");<GIWhere>attribute name isOperation=NOTOperator=;MUIScreen.SubcategoryIDmust point at tenant-real subcategory, not auto-created system-default. <Sql>re-execution warnings —<Sql>scripts are tracked by Name; bumping to_v2is silently skipped. For logic that must survive every publish, useCustomizationPlugin.PXDatabase.Execute().- DAC extension fields without
<EntityEndpoint>mapping —PUTreturns HTTP 200 + echoes value + silent-drops at commit. Three places must match: SQL column, DAC attribute, inline<EntityEndpoint>block. - C# CustomizationPlugin static checks — banned APIs (
WebConfigurationManager,HttpContext); SQL against GI* tables inUpdateDatabase(); SQL againstINUnit; INSERT/UPDATE/SELECT column refs on Acumatica system tables outside the allowlist. - DLL source reference auditing — gap-closer for compiled-DLL plugins: every plugin check runs on every
.csundersrc/<DllName>/whenproject.xmlreferences<File AppRelativePath="Bin\<DllName>.dll" />. - ISV prefix enforcement — when invoked with
--isv-prefix, validates that every<Graph ClassName=>follows your ISV convention. - Inline
<EntityEndpoint>block requirements — must be a top-level child of<Customization>inproject.xml; standaloneEntityEndpoint_*.xmlat zip root is NOT processed.
Every hard-fail check ships an escape-hatch comment marker for audited exceptions:
<!-- REVIEWED: gi-sql-safe — suppress validate_gi_sql -->
<!-- REVIEWED: inunit-sql-safe — suppress validate_inunit_sql -->
<!-- REVIEWED: schema-safe — suppress validate_plugin_sql_column_refs -->
What this linter is NOT
This linter is the deterministic check engine. It runs once, against one project, returns errors and warnings, exits.
It does not:
- Run continuously on every PR
- Push your customization to a sandbox tenant and run an end-to-end test suite before allowing the prod publish
- Take a snapshot of your current customization state with integrity verification before each publish (catching
merge=truepartial-publish corruption) - Roll back automatically when a publish fails midway
- Review the C# code in
CustomizationPlugin.UpdateDatabase()with AI to flag non-obvious correctness issues a static rule can't catch - Post
:rotating_light:Slack notifications when a publish fails - Enforce deploy windows (no schema changes during business hours)
- Coordinate co-publishes with ISV packages without inflating the
publishBeginpage count past Acumatica'sThreadAbortExceptionlimit - Issue + revoke per-VAR license keys with grace periods
All of that is AcuOps — the managed pipeline. $800/mo per Acumatica instance.
Founding partners
AcuOps is selecting 7–10 founding partners locked at $500/mo for the life of subscription in exchange for a published case study + reference availability. We're picking partners across VAR, ISV, in-house mid-market, and enterprise deployment patterns. → Apply
Contributing
Pull requests welcome. New rules should:
- Reference the originating production incident (AAR date, PR number, or both) in a comment
- Ship with both a positive example (
project.xmlsnippet that triggers the rule) and a negative example - Include an escape-hatch comment marker if the rule may have legitimate exceptions
Coding style is enforced by ruff. Run pip install -e ".[dev]" then ruff check src/.
License
Apache 2.0 — see LICENSE.
The Apache 2.0 patent grant protects all contributors from patent litigation over contributed code. This matters when the codebase encodes hard-won failure-mode knowledge from real production incidents.
Built by Studio B
Studio B (studiob.ai) runs Acumatica deployments at scale across its portfolio. acumatica-lint is the static-check engine extracted from the AcuOps pipeline — the same code paths Studio B uses against its own production deployments.
The full pipeline → acuops.com
Metadata
Release files for acumatica-lint 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| acumatica_lint-0.3.0.tar.gz | 81.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| acumatica_lint-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 156.1 kB
Release files / acumatica_lint-0.3.0.tar.gz
| Download URL | acumatica_lint-0.3.0.tar.gz |
|---|---|
| Size | 81.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1c9ae4789673c7b6152868625fdfeab5204de769affd56dd7f725c0cf5b6b206
|
|
BLAKE2b-256 checksum How to use checksums |
8ee6d4c892de010c54bd31895402e3092c65d4271a9665efc783b30503e58110
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 1, 2026.
Transparency logRelease files / acumatica_lint-0.3.0-py3-none-any.whl
| Download URL | acumatica_lint-0.3.0-py3-none-any.whl |
|---|---|
| Size | 74.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3cd689d685241fc4dc74f54446ead312024d589196d804b77152ccb562f9cfd1
|
|
BLAKE2b-256 checksum How to use checksums |
fe24e763055064e032447d523f98c0cd702a676c4fa6ea1b251254e27f328156
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 1, 2026.
Transparency logSecurity for acumatica-lint 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for acumatica_lint-0.3.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.