atlan-application-sdk-conformance 0.2.0__py3-none-any.whl
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.
- atlan_application_sdk_conformance-0.2.0.dist-info/METADATA +64 -0
- atlan_application_sdk_conformance-0.2.0.dist-info/RECORD +49 -0
- atlan_application_sdk_conformance-0.2.0.dist-info/WHEEL +4 -0
- atlan_application_sdk_conformance-0.2.0.dist-info/entry_points.txt +2 -0
- conformance/__init__.py +7 -0
- conformance/cli.py +126 -0
- conformance/docs/rules/ci.md +33 -0
- conformance/docs/rules/error-handling.md +283 -0
- conformance/docs/rules/logging.md +277 -0
- conformance/docs/schema-contract.md +255 -0
- conformance/package-lock.json +1760 -0
- conformance/package.json +18 -0
- conformance/programs/areas/ci.prose.md +47 -0
- conformance/programs/areas/error-handling.prose.md +55 -0
- conformance/programs/areas/logging.prose.md +47 -0
- conformance/programs/conformance-remediation.prose.md +85 -0
- conformance/programs/functions/detect-violations.prose.md +99 -0
- conformance/programs/functions/orthogonal-gate.prose.md +39 -0
- conformance/programs/functions/recheck-narrowest.prose.md +51 -0
- conformance/programs/functions/remediate-finding.prose.md +123 -0
- conformance/programs/patterns/detect-fix-recheck.prose.md +110 -0
- conformance/suite/__init__.py +27 -0
- conformance/suite/checks/__init__.py +1 -0
- conformance/suite/checks/actions_pinning.py +202 -0
- conformance/suite/checks/error_handling/__init__.py +212 -0
- conformance/suite/checks/error_handling/_checker.py +151 -0
- conformance/suite/checks/error_handling/_collect.py +37 -0
- conformance/suite/checks/error_handling/_constants.py +115 -0
- conformance/suite/checks/error_handling/_directives.py +131 -0
- conformance/suite/checks/error_handling/_helpers.py +225 -0
- conformance/suite/checks/error_handling/exception_chaining.py +53 -0
- conformance/suite/checks/error_handling/security.py +49 -0
- conformance/suite/checks/error_handling/silent_swallow.py +312 -0
- conformance/suite/checks/error_handling/untyped_raise.py +110 -0
- conformance/suite/rules/__init__.py +46 -0
- conformance/suite/rules/ci.py +27 -0
- conformance/suite/rules/error_handling.py +360 -0
- conformance/suite/rules/logging.py +336 -0
- conformance/suite/runner.py +240 -0
- conformance/suite/schema/__init__.py +78 -0
- conformance/suite/schema/builder.py +328 -0
- conformance/suite/schema/catalog.py +120 -0
- conformance/suite/schema/disposition.py +146 -0
- conformance/suite/schema/extensions.py +185 -0
- conformance/suite/schema/findings.py +74 -0
- conformance/suite/schema/sarif-schema-2.1.0.json +2882 -0
- conformance/suite/schema/sarif.py +360 -0
- conformance/suite/schema/validate.py +63 -0
- conformance/tools/generate_rule_docs.py +295 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: atlan-application-sdk-conformance
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Conformance suite, remediation programs, and CLI for the Atlan Application SDK
|
|
5
|
+
Author-email: Atlan App Team <connect@atlan.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Keywords: atlan,conformance,linting,remediation,sdk
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: jsonschema<5.0.0,>=4.23.0
|
|
19
|
+
Requires-Dist: pydantic<3.0.0,>=2.10.6
|
|
20
|
+
Provides-Extra: test
|
|
21
|
+
Requires-Dist: atlan-application-sdk; extra == 'test'
|
|
22
|
+
Requires-Dist: pytest-asyncio<2.0.0,>=1.4.0; extra == 'test'
|
|
23
|
+
Requires-Dist: pytest<10.0.0,>=8.3.3; extra == 'test'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# atlan-application-sdk-conformance
|
|
27
|
+
|
|
28
|
+
Dev-only conformance suite, remediation programs, and CLI for the
|
|
29
|
+
[Atlan Application SDK](https://pypi.org/project/atlan-application-sdk/).
|
|
30
|
+
|
|
31
|
+
**Do not add this as a production dependency.** It is intended for developer
|
|
32
|
+
machines and CI only.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv add --dev atlan-application-sdk-conformance
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Run the conformance suite
|
|
44
|
+
uv run atlan-application-sdk-conformance detect --repo . --series E,L,C --output report.sarif
|
|
45
|
+
|
|
46
|
+
# Get the path to bundled remediation programs (for SKILL.md / reactor)
|
|
47
|
+
uv run atlan-application-sdk-conformance programs-dir
|
|
48
|
+
|
|
49
|
+
# Regenerate rule docs
|
|
50
|
+
uv run atlan-application-sdk-conformance gen-rule-docs
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## In CI
|
|
54
|
+
|
|
55
|
+
Consumer repos should reference this package via the reusable workflow in
|
|
56
|
+
`atlanhq/application-sdk`:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
uses: atlanhq/application-sdk/.github/workflows/conformance-reusable.yaml@main
|
|
60
|
+
# No inputs required — the published PyPI package is used by default.
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
See `conformance/programs/conformance-remediation.prose.md` for the
|
|
64
|
+
`/remediate` skill entry contract.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
conformance/__init__.py,sha256=f5ihJyNJuyw1MkpgTuPWdcf1jsksR2lpGnE3FJg1S7Y,199
|
|
2
|
+
conformance/cli.py,sha256=ZaQ2CUbHhHo19S9T0tx6YD7NHOpihZDsItQ6_X9fs_U,4013
|
|
3
|
+
conformance/package-lock.json,sha256=F8EwDEjecgD82OM19F1YGdtrVvPOzIVAaJI5JdeMKtw,58365
|
|
4
|
+
conformance/package.json,sha256=egDs4ZNoIpfJiGKlOD95egYFDoc53fGcEqDxOp8VBa8,463
|
|
5
|
+
conformance/docs/schema-contract.md,sha256=v8g3hDvzH_yNgbZ3t9ZXY483wkI8JnnWuWQK_uQbpa4,11859
|
|
6
|
+
conformance/docs/rules/ci.md,sha256=PlEiRfDNaybMzbvDEepCmrbS4yy1XPfahhg3jCeqngw,1229
|
|
7
|
+
conformance/docs/rules/error-handling.md,sha256=Z40cuoBPXZD2UNK0qoJeme5Xg1xUCihClUyECuNdomM,12648
|
|
8
|
+
conformance/docs/rules/logging.md,sha256=fqnpi_bhgyFzWlimGUND8WanqKsVN0IhkexL3bGtjZA,11301
|
|
9
|
+
conformance/programs/conformance-remediation.prose.md,sha256=eYCuS_I5iUAr34UjdbvHPUBoSva5m1Eva_Lz9lZK1Mg,2812
|
|
10
|
+
conformance/programs/areas/ci.prose.md,sha256=08LhyngT5KbG1SMLNnddRs5v3qz0uaeJbZ9GVb5pa1w,1390
|
|
11
|
+
conformance/programs/areas/error-handling.prose.md,sha256=xPoDR7uvmtqoAFLfLLrA6JbryjnK5B1rCTHERrwV1FA,1792
|
|
12
|
+
conformance/programs/areas/logging.prose.md,sha256=6-6NtipMJYm4m7zaoUKD4YLnipenUfnzMRbyBWCeVhI,1407
|
|
13
|
+
conformance/programs/functions/detect-violations.prose.md,sha256=VEYLrb4GetCOllUgnO7Nv48Mt14EkrX0ZCGyrU3tfjk,4544
|
|
14
|
+
conformance/programs/functions/orthogonal-gate.prose.md,sha256=t55UmBgQazvkEBj-w4krZ5PISYKLMVG_9wBg6mu2QgQ,1298
|
|
15
|
+
conformance/programs/functions/recheck-narrowest.prose.md,sha256=T2AKpo0t8a91i8RXKFd_1k5L8V242Vn7LEMo-MsDRdg,1895
|
|
16
|
+
conformance/programs/functions/remediate-finding.prose.md,sha256=MG_m_PImjSJLrbhvuQr6rQaki6X8FXoHq3EK5v61xrQ,5376
|
|
17
|
+
conformance/programs/patterns/detect-fix-recheck.prose.md,sha256=IM_dSjpY-9bHz9Fn0p2mA-lRiiGzrU5JIcBNK4opj4U,3795
|
|
18
|
+
conformance/suite/__init__.py,sha256=Xi1PgHR4r2UpvFwCaHXLbzumDWm8mv6sLp_thsDHutk,885
|
|
19
|
+
conformance/suite/runner.py,sha256=Pm7aC2jW-2WxrnK6nb0xFyDSgbIinx_NnukM5LKZ2hI,8502
|
|
20
|
+
conformance/suite/checks/__init__.py,sha256=yTctRyi-506ulQ-KXjYBuJ7Z_rl7alyaZm_07XF2rzk,69
|
|
21
|
+
conformance/suite/checks/actions_pinning.py,sha256=zeQbbX30AuU0eGQV0i2bsju7oKHgTWhFwQeOvZvwtAE,6233
|
|
22
|
+
conformance/suite/checks/error_handling/__init__.py,sha256=5bjyvqdWlG1BNpGpUKhtvueje_B62KHtqi7afWG4TkU,6628
|
|
23
|
+
conformance/suite/checks/error_handling/_checker.py,sha256=rWUAOWfleKZIcp1LS7hyYrspB4kc8hzYhDVNIyuUOKA,5884
|
|
24
|
+
conformance/suite/checks/error_handling/_collect.py,sha256=pwlG2cpOakvtQzZuAjDxyHuQKBW1wPj00ClWcOLmFrk,1420
|
|
25
|
+
conformance/suite/checks/error_handling/_constants.py,sha256=3rCAoaS2gT8DY0HUkgXtDr-xB9qYs5TJQdWb8rGF08I,3462
|
|
26
|
+
conformance/suite/checks/error_handling/_directives.py,sha256=Vam0noPsaxUZS6UBT6kN74-RaLMoTIAiBvwJfrYEMkQ,5452
|
|
27
|
+
conformance/suite/checks/error_handling/_helpers.py,sha256=crgvWd0HCg7eMnC3FfvHvuGLE88qFSltrIa_7gQDyAE,7101
|
|
28
|
+
conformance/suite/checks/error_handling/exception_chaining.py,sha256=GR56sA2vnk5Ke4u6Gv16dd6aouzhPZz79pxQdoNrWWU,2385
|
|
29
|
+
conformance/suite/checks/error_handling/security.py,sha256=HCLvFBo63W8N4K4qouc6TLDX5Buy1ismvMlQpD5waWc,2066
|
|
30
|
+
conformance/suite/checks/error_handling/silent_swallow.py,sha256=ywvst6fcKOuCbfBC8umH25WMgVG1Nm3AIcZSkZ093VU,13969
|
|
31
|
+
conformance/suite/checks/error_handling/untyped_raise.py,sha256=93mTp2MYMKwTmvEk7QOLxLQ5jF7HIBRyj006XgmQJdU,4611
|
|
32
|
+
conformance/suite/rules/__init__.py,sha256=BUpntBeRsAVXRC1fyHhrxjjZmVp1KNXOGkDj1vcaWYg,1582
|
|
33
|
+
conformance/suite/rules/ci.py,sha256=YoDDR2a26kGxZRYIeiE6yMza_DWxxu301Hp1EfJSxb8,1159
|
|
34
|
+
conformance/suite/rules/error_handling.py,sha256=dwCWFdRp4ZE5iKqU-WUgWzFzd4IziVzA4u49mkbr-G0,18091
|
|
35
|
+
conformance/suite/rules/logging.py,sha256=KjW4tM5P1uFdtbdq5XL8b9HONcENt31e_-bxNrjPgwM,15925
|
|
36
|
+
conformance/suite/schema/__init__.py,sha256=hlr9LDcKxxChzADqdET8EOYFXgklqE0ewjP6hvOqEmQ,2048
|
|
37
|
+
conformance/suite/schema/builder.py,sha256=DCGPa_3hy_C6JXCGiMg7A49lPAHvuYcdH4FC8PDyV78,11323
|
|
38
|
+
conformance/suite/schema/catalog.py,sha256=8Yj_96zE0U8ORYTjlq9sLN-7T8K5-0SwTRl79NEDX9Q,4046
|
|
39
|
+
conformance/suite/schema/disposition.py,sha256=YMlzNwnrWp4BaMUI6V4IiOogPoVdK-hWWeZLZLyA9C0,5770
|
|
40
|
+
conformance/suite/schema/extensions.py,sha256=dEXl2ZM-UDIxtMQF258duYx8iQrtJpPfIepEdi_l0dY,6547
|
|
41
|
+
conformance/suite/schema/findings.py,sha256=AJaN3S7I3MKPmJ8nGHK6qlpqXhOuPJJtH9Yg7g8wQjA,2330
|
|
42
|
+
conformance/suite/schema/sarif-schema-2.1.0.json,sha256=fJaI8KHEpOFknsx4UhCH5mRynB3_Vu6CEv8ZXHsWEyo,111720
|
|
43
|
+
conformance/suite/schema/sarif.py,sha256=gokZ_e-N2VK_rJRWAYdhjW75vPuLtyrJ9Sct8mFnrW4,11276
|
|
44
|
+
conformance/suite/schema/validate.py,sha256=GpiJG-mdeisIc6nOqMBef6xrvWqTtoiEmiZz32N56xw,2112
|
|
45
|
+
conformance/tools/generate_rule_docs.py,sha256=lJpAcjzQEQiCLkWU6KXHHiblzok0nHLWgTpk2vFxDuI,9717
|
|
46
|
+
atlan_application_sdk_conformance-0.2.0.dist-info/METADATA,sha256=Sd-evkrO9bxMShgdSgOlzNOAqgzQeaT-DFththjZuQw,2166
|
|
47
|
+
atlan_application_sdk_conformance-0.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
48
|
+
atlan_application_sdk_conformance-0.2.0.dist-info/entry_points.txt,sha256=qpEna_KV_1dF4C4Gdsv4ysCYT33OR8KuVS18PMBTJZ8,75
|
|
49
|
+
atlan_application_sdk_conformance-0.2.0.dist-info/RECORD,,
|
conformance/__init__.py
ADDED
conformance/cli.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Entry point for the atlan-application-sdk-conformance CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _cmd_detect(argv: list[str]) -> int:
|
|
9
|
+
from conformance.suite.runner import main
|
|
10
|
+
|
|
11
|
+
return main(argv)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _cmd_programs_dir(_argv: list[str]) -> int:
|
|
15
|
+
import importlib.resources as _ir
|
|
16
|
+
import pathlib
|
|
17
|
+
|
|
18
|
+
programs = _ir.files("conformance") / "programs"
|
|
19
|
+
# Resolve to a real filesystem path (works for both installed wheels and
|
|
20
|
+
# editable installs where the files are already on disk).
|
|
21
|
+
try:
|
|
22
|
+
ctx = _ir.as_file(programs)
|
|
23
|
+
with ctx as p:
|
|
24
|
+
print(str(p))
|
|
25
|
+
except (FileNotFoundError, ModuleNotFoundError):
|
|
26
|
+
# Fallback: direct path (editable installs)
|
|
27
|
+
here = pathlib.Path(__file__).parent
|
|
28
|
+
print(str(here / "programs"))
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _cmd_gen_rule_docs(argv: list[str]) -> int:
|
|
33
|
+
from conformance.tools.generate_rule_docs import main
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
main(argv)
|
|
37
|
+
return 0
|
|
38
|
+
except SystemExit as e:
|
|
39
|
+
return int(e.code) if e.code is not None else 0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _cmd_remediate(argv: list[str]) -> int:
|
|
43
|
+
"""Print the resolved programs path + version, then exit.
|
|
44
|
+
|
|
45
|
+
The actual remediation loop is driven by the SKILL.md shim which reads
|
|
46
|
+
the .prose.md contracts from the printed programs directory.
|
|
47
|
+
"""
|
|
48
|
+
import pathlib
|
|
49
|
+
|
|
50
|
+
from conformance import __version__
|
|
51
|
+
|
|
52
|
+
here = pathlib.Path(__file__).parent
|
|
53
|
+
programs = here / "programs"
|
|
54
|
+
print(f"atlan-application-sdk-conformance {__version__}")
|
|
55
|
+
print(f"programs: {programs}")
|
|
56
|
+
print(f"entry: {programs / 'conformance-remediation.prose.md'}")
|
|
57
|
+
return 0
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# The SKILL.md written by `bootstrap`. Keep it minimal and stable — the real
|
|
61
|
+
# logic lives in the package; this shim just locates and invokes it.
|
|
62
|
+
_SKILL_MD = """\
|
|
63
|
+
---
|
|
64
|
+
name: remediate
|
|
65
|
+
description: Drive the conformance remediation loop (validators + OpenProse programs from the atlan-application-sdk-conformance package)
|
|
66
|
+
argument-hint: "[--area error-handling|logging|ci] [--strict] [path]"
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
1. Resolve programs dir:
|
|
70
|
+
- Inside a connector repo: `PROGRAMS=$(uv run atlan-application-sdk-conformance programs-dir)`
|
|
71
|
+
- Anywhere else: `PROGRAMS=$(uvx atlan-application-sdk-conformance@latest programs-dir)`
|
|
72
|
+
2. Read `$PROGRAMS/conformance-remediation.prose.md` and execute it as the entry contract.
|
|
73
|
+
3. All gated re-checks call `atlan-application-sdk-conformance detect` — follow the .prose.md exactly.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _cmd_bootstrap(argv: list[str]) -> int:
|
|
78
|
+
"""Write .claude/skills/remediate/SKILL.md in the current repo (or --force to overwrite)."""
|
|
79
|
+
import pathlib
|
|
80
|
+
|
|
81
|
+
force = "--force" in argv
|
|
82
|
+
dest = pathlib.Path.cwd() / ".claude" / "skills" / "remediate" / "SKILL.md"
|
|
83
|
+
|
|
84
|
+
if dest.exists() and not force:
|
|
85
|
+
print(f"already installed: {dest} (pass --force to overwrite)")
|
|
86
|
+
return 0
|
|
87
|
+
|
|
88
|
+
existed = dest.exists()
|
|
89
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
dest.write_text(_SKILL_MD)
|
|
91
|
+
action = "updated" if existed else "installed"
|
|
92
|
+
print(f"{action}: {dest}")
|
|
93
|
+
return 0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
_COMMANDS = {
|
|
97
|
+
"detect": _cmd_detect,
|
|
98
|
+
"programs-dir": _cmd_programs_dir,
|
|
99
|
+
"gen-rule-docs": _cmd_gen_rule_docs,
|
|
100
|
+
"remediate": _cmd_remediate,
|
|
101
|
+
"bootstrap": _cmd_bootstrap,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_USAGE = """\
|
|
105
|
+
usage: atlan-application-sdk-conformance <command> [args]
|
|
106
|
+
|
|
107
|
+
commands:
|
|
108
|
+
detect Run the conformance suite and emit SARIF
|
|
109
|
+
programs-dir Print the absolute path to the bundled .prose.md programs
|
|
110
|
+
gen-rule-docs Regenerate rule docs from Python rule definitions
|
|
111
|
+
remediate Print programs path + version banner (SKILL.md drives execution)
|
|
112
|
+
bootstrap Write ~/.claude/skills/remediate/SKILL.md (--force to overwrite)
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def main() -> None:
|
|
117
|
+
if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help"):
|
|
118
|
+
print(_USAGE)
|
|
119
|
+
sys.exit(0)
|
|
120
|
+
|
|
121
|
+
cmd = sys.argv[1]
|
|
122
|
+
if cmd not in _COMMANDS:
|
|
123
|
+
print(f"error: unknown command '{cmd}'\n{_USAGE}", file=sys.stderr)
|
|
124
|
+
sys.exit(1)
|
|
125
|
+
|
|
126
|
+
sys.exit(_COMMANDS[cmd](sys.argv[2:]))
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<!-- AUTO-GENERATED — do not edit this file directly.
|
|
2
|
+
Source of truth: conformance/suite/rules/ci.py
|
|
3
|
+
To regenerate: uv run poe generate-rule-docs
|
|
4
|
+
To check CI staleness: uv run poe generate-rule-docs --check -->
|
|
5
|
+
|
|
6
|
+
# CI/Workflow Supply-Chain Rules (C-series)
|
|
7
|
+
|
|
8
|
+
**1 rule** · Checker: `suite.checks.actions_pinning` and related workflow checks (static)
|
|
9
|
+
|
|
10
|
+
Suppress a finding on the violating line or the line directly above it:
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
# conformance: ignore[C001] intentional: org-internal action
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
| ID | Name | Tier | Category | Autofixable | Since |
|
|
17
|
+
|---|---|---|---|---|---|
|
|
18
|
+
| [C001](#c001) | `UnpinnedActionReference` | `block` | `supply-chain` | yes | 3.16.0 |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## C001 — `UnpinnedActionReference` {#c001}
|
|
23
|
+
|
|
24
|
+
**Tier:** `block` · **Category:** `supply-chain` · **Autofixable:** yes · **Since:** 3.16.0
|
|
25
|
+
|
|
26
|
+
> External GitHub Action not pinned to a full commit digest
|
|
27
|
+
|
|
28
|
+
External actions reused via `uses:` must be pinned to a full-length commit SHA (digest),
|
|
29
|
+
never a mutable tag (@v4) or branch (@main). A tag can be re-pointed to malicious code
|
|
30
|
+
after review. Actions in the `atlanhq/` org are exempt (they intentionally track @main);
|
|
31
|
+
local `./` composite-action refs are exempt (no version to pin).
|
|
32
|
+
|
|
33
|
+
---
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
<!-- AUTO-GENERATED — do not edit this file directly.
|
|
2
|
+
Source of truth: conformance/suite/rules/error_handling.py
|
|
3
|
+
To regenerate: uv run poe generate-rule-docs
|
|
4
|
+
To check CI staleness: uv run poe generate-rule-docs --check -->
|
|
5
|
+
|
|
6
|
+
# Error-Handling Rules (E-series)
|
|
7
|
+
|
|
8
|
+
**18 rules** · Checker: `suite.checks.error_handling` (AST-based)
|
|
9
|
+
|
|
10
|
+
Suppress a finding on the violating line or the line directly above it:
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
# conformance: ignore[E012] intentional: stdlib interop
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
| ID | Name | Tier | Category | Autofixable | Since |
|
|
17
|
+
|---|---|---|---|---|---|
|
|
18
|
+
| [E001](#e001) | `BareExceptPass` | `block` | `silent-swallow` | — | 3.16.0 |
|
|
19
|
+
| [E002](#e002) | `TypedExceptPass` | `block` | `silent-swallow` | — | 3.16.0 |
|
|
20
|
+
| [E003](#e003) | `BroadContextlibSuppress` | `warn` | `silent-swallow` | — | 3.16.0 |
|
|
21
|
+
| [E004](#e004) | `BroadExceptClause` | `warn` | `overly-broad-catch` | — | 3.16.0 |
|
|
22
|
+
| [E005](#e005) | `ExceptBlockMissingExcInfo` | `warn` | `missing-traceback` | yes | 3.16.0 |
|
|
23
|
+
| [E006](#e006) | `BareExceptWithBody` | `block` | `silent-swallow` | — | 3.16.0 |
|
|
24
|
+
| [E007](#e007) | `ErrorToReturnValue` | `warn` | `error-to-return-value` | — | 3.16.0 |
|
|
25
|
+
| [E008](#e008) | `ImportErrorWithoutLogging` | `warn` | `optional-import` | — | 3.16.0 |
|
|
26
|
+
| [E009](#e009) | `ExceptBlockOnlyAssigns` | `warn` | `error-to-return-value` | — | 3.16.0 |
|
|
27
|
+
| [E010](#e010) | `AsyncioGatherExceptionsUnexamined` | `warn` | `asyncio-unexamined` | — | 3.16.0 |
|
|
28
|
+
| [E011](#e011) | `LoggingFilterUnsafeBody` | `warn` | `filter-safety` | — | 3.17.0 |
|
|
29
|
+
| [E012](#e012) | `UntypedBuiltinRaise` | `warn` | `untyped-raise` | — | 3.16.0 |
|
|
30
|
+
| [E013](#e013) | `LegacyAtlanErrorRaise` | `block` | `legacy-raise` | — | 3.16.0 |
|
|
31
|
+
| [E014](#e014) | `ExceptLoopControlSwallow` | `warn` | `silent-swallow` | — | 3.17.0 |
|
|
32
|
+
| [E015](#e015) | `ExceptionTextInErrorMessage` | `warn` | `error-message-hygiene` | — | 3.17.0 |
|
|
33
|
+
| [E016](#e016) | `MissingExceptionChaining` | `warn` | `exception-chaining` | yes | 3.17.0 |
|
|
34
|
+
| [E017](#e017) | `SecretNamedEvidenceKey` | `block` | `security` | — | 3.17.0 |
|
|
35
|
+
| [E018](#e018) | `BareParentLeafRaise` | `warn` | `untyped-raise` | — | 3.17.0 |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## E001 — `BareExceptPass` {#e001}
|
|
40
|
+
|
|
41
|
+
**Tier:** `block` · **Category:** `silent-swallow` · **Autofixable:** — · **Since:** 3.16.0
|
|
42
|
+
|
|
43
|
+
> Bare 'except: pass' silently discards every exception
|
|
44
|
+
|
|
45
|
+
A bare `except: pass` catches KeyboardInterrupt, SystemExit, and GeneratorExit and
|
|
46
|
+
discards them with no trace. This is the hardest class of bugs to debug. Replace with
|
|
47
|
+
a typed catch that at minimum logs the error with `exc_info=True`. Never acceptable —
|
|
48
|
+
even cleanup paths should log at DEBUG.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## E002 — `TypedExceptPass` {#e002}
|
|
53
|
+
|
|
54
|
+
**Tier:** `block` · **Category:** `silent-swallow` · **Autofixable:** — · **Since:** 3.16.0
|
|
55
|
+
|
|
56
|
+
> Typed 'except SomeError: pass' discards exception silently
|
|
57
|
+
|
|
58
|
+
A typed catch that still discards silently loses the stack trace entirely. Acceptable
|
|
59
|
+
only for truly trivial best-effort operations where failure is 100% expected AND the
|
|
60
|
+
surrounding code handles the missing result, AND there is a comment explaining the
|
|
61
|
+
reasoning.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## E003 — `BroadContextlibSuppress` {#e003}
|
|
66
|
+
|
|
67
|
+
**Tier:** `warn` · **Category:** `silent-swallow` · **Autofixable:** — · **Since:** 3.16.0
|
|
68
|
+
|
|
69
|
+
> contextlib.suppress() — check whether scope is too broad
|
|
70
|
+
|
|
71
|
+
`contextlib.suppress(Exception)` or `suppress(BaseException)` is HIGH severity; narrow
|
|
72
|
+
`suppress(FileNotFoundError)` on a cleanup path is acceptable. The checker must inspect
|
|
73
|
+
the suppressed exception type before classifying.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## E004 — `BroadExceptClause` {#e004}
|
|
78
|
+
|
|
79
|
+
**Tier:** `warn` · **Category:** `overly-broad-catch` · **Autofixable:** — · **Since:** 3.16.0
|
|
80
|
+
|
|
81
|
+
> Overly broad 'except Exception/BaseException' without exc_info
|
|
82
|
+
|
|
83
|
+
Catches everything but the specific type is unknown. HIGH severity when not logged;
|
|
84
|
+
MEDIUM when logged but missing `exc_info=True`. Acceptable only at top-level handlers
|
|
85
|
+
(worker loops, HTTP handlers) when properly logged with `exc_info=True`.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## E005 — `ExceptBlockMissingExcInfo` {#e005}
|
|
90
|
+
|
|
91
|
+
**Tier:** `warn` · **Category:** `missing-traceback` · **Autofixable:** yes · **Since:** 3.16.0
|
|
92
|
+
|
|
93
|
+
> except block logs without exc_info=True — stack trace discarded
|
|
94
|
+
|
|
95
|
+
The message is logged but the stack trace is lost. Add `exc_info=True` to every
|
|
96
|
+
`logger.warning()` / `logger.error()` call inside an except block. `logger.exception()`
|
|
97
|
+
is exempt (it implies `exc_info=True`).
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## E006 — `BareExceptWithBody` {#e006}
|
|
102
|
+
|
|
103
|
+
**Tier:** `block` · **Category:** `silent-swallow` · **Autofixable:** — · **Since:** 3.16.0
|
|
104
|
+
|
|
105
|
+
> Bare 'except:' (no type) — catches SystemExit and KeyboardInterrupt
|
|
106
|
+
|
|
107
|
+
Like P001 but the block may have a body. Still catches KeyboardInterrupt and
|
|
108
|
+
SystemExit. Always specify at least `except Exception:`.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## E007 — `ErrorToReturnValue` {#e007}
|
|
113
|
+
|
|
114
|
+
**Tier:** `warn` · **Category:** `error-to-return-value` · **Autofixable:** — · **Since:** 3.16.0
|
|
115
|
+
|
|
116
|
+
> except block returns a value without logging — error hidden
|
|
117
|
+
|
|
118
|
+
Exception is converted to a return value (None, {}, [], False) with no trace. Callers
|
|
119
|
+
see a wrong result with no idea why. At minimum log before returning; prefer raising a
|
|
120
|
+
domain-specific exception instead.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## E008 — `ImportErrorWithoutLogging` {#e008}
|
|
125
|
+
|
|
126
|
+
**Tier:** `warn` · **Category:** `optional-import` · **Autofixable:** — · **Since:** 3.16.0
|
|
127
|
+
|
|
128
|
+
> except ImportError without logging — environment issues hidden
|
|
129
|
+
|
|
130
|
+
Optional-dependency guard. Acceptable when the import is genuinely optional AND the
|
|
131
|
+
fallback path is correct AND there is a comment. Log at DEBUG if the module is
|
|
132
|
+
preferred but not required. Flag if the module is expected to be present (will fail
|
|
133
|
+
later with a confusing AttributeError).
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## E009 — `ExceptBlockOnlyAssigns` {#e009}
|
|
138
|
+
|
|
139
|
+
**Tier:** `warn` · **Category:** `error-to-return-value` · **Autofixable:** — · **Since:** 3.16.0
|
|
140
|
+
|
|
141
|
+
> except block only assigns a variable — error hidden with no log
|
|
142
|
+
|
|
143
|
+
Exception sets a flag or default value with no trace. Combines P007's error-hiding with
|
|
144
|
+
no logging. Add a `logger.warning(..., exc_info=True)` before the assignment.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## E010 — `AsyncioGatherExceptionsUnexamined` {#e010}
|
|
149
|
+
|
|
150
|
+
**Tier:** `warn` · **Category:** `asyncio-unexamined` · **Autofixable:** — · **Since:** 3.16.0
|
|
151
|
+
|
|
152
|
+
> asyncio.gather(return_exceptions=True) results not checked for exceptions
|
|
153
|
+
|
|
154
|
+
`return_exceptions=True` returns exception instances as values in the result list. If
|
|
155
|
+
the list is not subsequently inspected for `Exception` instances, errors vanish
|
|
156
|
+
silently. The pattern is only a bug when results are not checked;
|
|
157
|
+
`return_exceptions=True` itself is acceptable.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## E011 — `LoggingFilterUnsafeBody` {#e011}
|
|
162
|
+
|
|
163
|
+
**Tier:** `warn` · **Category:** `filter-safety` · **Autofixable:** — · **Since:** 3.17.0
|
|
164
|
+
|
|
165
|
+
> logging.Filter.filter() body not wrapped in try/except — can crash caller
|
|
166
|
+
|
|
167
|
+
`Logger.handle()` calls `self.filter(record)` with no surrounding try/except — unlike
|
|
168
|
+
handler errors, filter exceptions are NOT caught by `handleError()`. An unguarded
|
|
169
|
+
attribute access (`record.custom_field`) or any external call that can raise will
|
|
170
|
+
propagate directly to the code that called `logger.info()` (or similar), crashing the
|
|
171
|
+
caller. Wrap the entire `filter()` body in try/except with a safe fallback (return True
|
|
172
|
+
to pass-through, False to drop). Use `getattr(record, 'field', default)` for optional
|
|
173
|
+
record attributes. Never acceptable — filter methods must never let exceptions
|
|
174
|
+
propagate.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## E012 — `UntypedBuiltinRaise` {#e012}
|
|
179
|
+
|
|
180
|
+
**Tier:** `warn` · **Category:** `untyped-raise` · **Autofixable:** — · **Since:** 3.16.0
|
|
181
|
+
|
|
182
|
+
> raise ValueError/RuntimeError/... where a typed AppError applies
|
|
183
|
+
|
|
184
|
+
SDK code raises a bare Python builtin. The Automation Engine receives an opaque string
|
|
185
|
+
— no category, code, audience, or retryable field. Dashboards are blind; on-call routing
|
|
186
|
+
is impossible. Use the typed error leaf from `application_sdk.errors`. Acceptable only
|
|
187
|
+
inside dataclass `__post_init__` / stdlib validator methods where Python semantics
|
|
188
|
+
require `TypeError`/`ValueError` for stdlib interoperability.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## E013 — `LegacyAtlanErrorRaise` {#e013}
|
|
193
|
+
|
|
194
|
+
**Tier:** `block` · **Category:** `legacy-raise` · **Autofixable:** — · **Since:** 3.16.0
|
|
195
|
+
|
|
196
|
+
> raise ClientError/ApiError/... (deprecated AtlanError stack)
|
|
197
|
+
|
|
198
|
+
`AtlanError` and its subclasses emit a `DeprecationWarning` at construction time and
|
|
199
|
+
reach AE as opaque strings. They produce no typed wire envelope. Scheduled for removal
|
|
200
|
+
in v4.0. Replace with the appropriate leaf from `application_sdk.errors`.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## E014 — `ExceptLoopControlSwallow` {#e014}
|
|
205
|
+
|
|
206
|
+
**Tier:** `warn` · **Category:** `silent-swallow` · **Autofixable:** — · **Since:** 3.17.0
|
|
207
|
+
|
|
208
|
+
> except block exits loop silently (continue/break) without logging
|
|
209
|
+
|
|
210
|
+
An `except` block inside a loop whose body is only `continue`, `break`, or `pass` — with
|
|
211
|
+
no logging call — silently swallows the exception and resumes or exits the iteration.
|
|
212
|
+
This is the loop-body twin of P001/P002 (ruff S112 family). At minimum log at DEBUG
|
|
213
|
+
before the loop control statement; if the exception signals a genuine error, re-raise or
|
|
214
|
+
log at WARNING/ERROR with `exc_info=True`.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## E015 — `ExceptionTextInErrorMessage` {#e015}
|
|
219
|
+
|
|
220
|
+
**Tier:** `warn` · **Category:** `error-message-hygiene` · **Autofixable:** — · **Since:** 3.17.0
|
|
221
|
+
|
|
222
|
+
> Caught exception text interpolated into typed error message= — leaks unsanitised text
|
|
223
|
+
|
|
224
|
+
A typed `AppError` raise whose `message=` keyword value embeds the caught exception via
|
|
225
|
+
an f-string (`f'…{exc}…'`), `str(exc)`, or `repr(exc)` — see typed-error-prescription.md
|
|
226
|
+
§6. This leaks unsanitised, potentially user-facing text from an upstream library into
|
|
227
|
+
a field that is displayed to operators and indexed in dashboards. It also breaks
|
|
228
|
+
aggregation by collapsing distinct failure modes into one variable-text bucket. Place
|
|
229
|
+
the exception context in a typed evidence field (`cause=exc`, `network_error=str(exc)`)
|
|
230
|
+
and keep `message=` a stable human summary.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## E016 — `MissingExceptionChaining` {#e016}
|
|
235
|
+
|
|
236
|
+
**Tier:** `warn` · **Category:** `exception-chaining` · **Autofixable:** yes · **Since:** 3.17.0
|
|
237
|
+
|
|
238
|
+
> raise inside except block missing 'from exc' cause — breaks exception chain
|
|
239
|
+
|
|
240
|
+
A non-bare `raise` inside an `except … as e:` block that does not include `from e` (or
|
|
241
|
+
`from None`). Without explicit chaining, Python attaches the original as `__context__`
|
|
242
|
+
with `__suppress_context__=False` — the chain is preserved at the interpreter level but
|
|
243
|
+
AE's wire serialiser only follows the `__cause__` chain, so the original exception is
|
|
244
|
+
lost in dashboards. Fix: `raise NewError(...) from e`. Use `from None` only when
|
|
245
|
+
intentionally hiding the original (requires a comment explaining why). Acceptable
|
|
246
|
+
patterns: bare `raise` (re-raise), `raise X() from e` (already chained), `raise X() from
|
|
247
|
+
None` (intentional suppression).
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## E017 — `SecretNamedEvidenceKey` {#e017}
|
|
252
|
+
|
|
253
|
+
**Tier:** `block` · **Category:** `security` · **Autofixable:** — · **Since:** 3.17.0
|
|
254
|
+
|
|
255
|
+
> Error evidence kwarg ending in _secret/_password/_token — rejected by wire layer at runtime
|
|
256
|
+
|
|
257
|
+
An error construction call that passes a keyword argument whose name ends in `_secret`,
|
|
258
|
+
`_password`, or `_token` — see `application_sdk.errors.wire` §6. The wire layer
|
|
259
|
+
actively rejects these suffixes at runtime (`ValueError`) to prevent credential leakage
|
|
260
|
+
into logs, dashboards, and SARIF reports. Static detection means the bug is caught
|
|
261
|
+
before any code runs. Rename the evidence field to a safe key (e.g. `credential_name`,
|
|
262
|
+
`token_type`) or pass the value via `cause=exc`.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## E018 — `BareParentLeafRaise` {#e018}
|
|
267
|
+
|
|
268
|
+
**Tier:** `warn` · **Category:** `untyped-raise` · **Autofixable:** — · **Since:** 3.17.0
|
|
269
|
+
|
|
270
|
+
> Raising a bare AppError leaf class without a domain subclass overriding code
|
|
271
|
+
|
|
272
|
+
Raising a parent leaf directly (`InternalError(...)`, `InvalidInputError(...)`) without
|
|
273
|
+
a domain-specific subclass that overrides `code` collapses all failure modes for a given
|
|
274
|
+
category into one dashboard bucket — impossible to route, alert on, or triage
|
|
275
|
+
individually. See typed-error-prescription.md §4. Only sanctioned bare-parent form:
|
|
276
|
+
`InternalError(classification_pending=True)` — used exclusively as a temporary
|
|
277
|
+
placeholder during migration, never in production-stable code. For all other sites:
|
|
278
|
+
define a domain subclass that overrides `code` with a specific constant (e.g.
|
|
279
|
+
`ENGINE_NOT_INITIALIZED`). Note: this rule is WARN tier because some bare-parent raises
|
|
280
|
+
may be legitimate in small apps or during active migration. Review findings before
|
|
281
|
+
suppressing.
|
|
282
|
+
|
|
283
|
+
---
|