Skip to main content

acumatica-lint

PyPI License: Apache 2.0 Python 3.10+

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 without IF NOT EXISTS guards — 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's CustomizationPlugin allowlist. Miss either and the GI silently catalog-excludes, returning HTTP 404 from /OData/{tenant}/$metadata.
  • SiteMap orphans (ParentID="00000000-...") — invisible in SM205010 (Access Rights by Role), ungrantable, OData 404.
  • Modern GI format requirements (24.208+) — GITable.Name must be fully-qualified DAC name; <GIWhere Condition> must use Acumatica's vocabulary ("E ", "NE", "GE", etc., NOT "EQ"); <GIWhere> attribute name is Operation= NOT Operator=; MUIScreen.SubcategoryID must point at tenant-real subcategory, not auto-created system-default.
  • <Sql> re-execution warnings — <Sql> scripts are tracked by Name; bumping to _v2 is silently skipped. For logic that must survive every publish, use CustomizationPlugin.PXDatabase.Execute().
  • DAC extension fields without <EntityEndpoint> mapping — PUT returns 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 in UpdateDatabase(); SQL against INUnit; 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 .cs under src/<DllName>/ when project.xml references <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> in project.xml; standalone EntityEndpoint_*.xml at 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=true partial-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 publishBegin page count past Acumatica's ThreadAbortException limit
  • 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:

  1. Reference the originating production incident (AAR date, PR number, or both) in a comment
  2. Ship with both a positive example (project.xml snippet that triggers the rule) and a negative example
  3. 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)

Source distribution for acumatica-lint 0.3.0
File Size Uploaded Provenance
acumatica_lint-0.3.0.tar.gz 81.9 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for acumatica-lint 0.3.0
File Interpreter ABI Platform
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page