deliverability-guard 0.1.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 (110) hide show
  1. deliverability_guard-0.1.0/.editorconfig +21 -0
  2. deliverability_guard-0.1.0/.env.example +5 -0
  3. deliverability_guard-0.1.0/.gitattributes +8 -0
  4. deliverability_guard-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +58 -0
  5. deliverability_guard-0.1.0/.github/ISSUE_TEMPLATE/config.yml +1 -0
  6. deliverability_guard-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +29 -0
  7. deliverability_guard-0.1.0/.github/dependabot.yml +23 -0
  8. deliverability_guard-0.1.0/.github/pull_request_template.md +25 -0
  9. deliverability_guard-0.1.0/.github/workflows/ci.yml +47 -0
  10. deliverability_guard-0.1.0/.github/workflows/release.yml +133 -0
  11. deliverability_guard-0.1.0/.github/workflows/zizmor.yml +39 -0
  12. deliverability_guard-0.1.0/.gitignore +40 -0
  13. deliverability_guard-0.1.0/.pre-commit-config.yaml +16 -0
  14. deliverability_guard-0.1.0/.vscode/extensions.json +3 -0
  15. deliverability_guard-0.1.0/AGENTS.md +67 -0
  16. deliverability_guard-0.1.0/BUILD-PLAN.md +406 -0
  17. deliverability_guard-0.1.0/CHANGELOG.md +98 -0
  18. deliverability_guard-0.1.0/CLAUDE.md +1 -0
  19. deliverability_guard-0.1.0/CODE_OF_CONDUCT.md +50 -0
  20. deliverability_guard-0.1.0/CONTRIBUTING.md +53 -0
  21. deliverability_guard-0.1.0/LICENSE +21 -0
  22. deliverability_guard-0.1.0/PKG-INFO +153 -0
  23. deliverability_guard-0.1.0/README.md +131 -0
  24. deliverability_guard-0.1.0/SECURITY.md +25 -0
  25. deliverability_guard-0.1.0/config/thresholds.example.yml +48 -0
  26. deliverability_guard-0.1.0/docs/decisions/0000-template.md +78 -0
  27. deliverability_guard-0.1.0/docs/decisions/0001-record-architecture-decisions.md +90 -0
  28. deliverability_guard-0.1.0/docs/decisions/0002-beta-binomial-hierarchical-pooling.md +155 -0
  29. deliverability_guard-0.1.0/docs/decisions/0003-never-auto-resume-after-pause.md +126 -0
  30. deliverability_guard-0.1.0/docs/demo.gif +0 -0
  31. deliverability_guard-0.1.0/docs/limits.md +86 -0
  32. deliverability_guard-0.1.0/docs/plans/going-public-checklist.md +46 -0
  33. deliverability_guard-0.1.0/docs/postmaster-verdicts.md +204 -0
  34. deliverability_guard-0.1.0/docs/statistics.md +173 -0
  35. deliverability_guard-0.1.0/docs/threat-model.md +57 -0
  36. deliverability_guard-0.1.0/examples/demo.py +93 -0
  37. deliverability_guard-0.1.0/examples/demo.tape +14 -0
  38. deliverability_guard-0.1.0/pyproject.toml +110 -0
  39. deliverability_guard-0.1.0/src/deliverability_guard/__init__.py +7 -0
  40. deliverability_guard-0.1.0/src/deliverability_guard/audit/__init__.py +1 -0
  41. deliverability_guard-0.1.0/src/deliverability_guard/audit/log.py +228 -0
  42. deliverability_guard-0.1.0/src/deliverability_guard/cli.py +7 -0
  43. deliverability_guard-0.1.0/src/deliverability_guard/engine/__init__.py +1 -0
  44. deliverability_guard-0.1.0/src/deliverability_guard/engine/breaker.py +365 -0
  45. deliverability_guard-0.1.0/src/deliverability_guard/engine/changepoint.py +79 -0
  46. deliverability_guard-0.1.0/src/deliverability_guard/engine/posterior.py +208 -0
  47. deliverability_guard-0.1.0/src/deliverability_guard/engine/state.py +98 -0
  48. deliverability_guard-0.1.0/src/deliverability_guard/identity/__init__.py +1 -0
  49. deliverability_guard-0.1.0/src/deliverability_guard/identity/feedback_id.py +108 -0
  50. deliverability_guard-0.1.0/src/deliverability_guard/identity/subdomain_advisor.py +96 -0
  51. deliverability_guard-0.1.0/src/deliverability_guard/loops/__init__.py +1 -0
  52. deliverability_guard-0.1.0/src/deliverability_guard/loops/fast.py +79 -0
  53. deliverability_guard-0.1.0/src/deliverability_guard/loops/slow.py +112 -0
  54. deliverability_guard-0.1.0/src/deliverability_guard/providers/__init__.py +1 -0
  55. deliverability_guard-0.1.0/src/deliverability_guard/providers/_parsing.py +78 -0
  56. deliverability_guard-0.1.0/src/deliverability_guard/providers/_retry.py +63 -0
  57. deliverability_guard-0.1.0/src/deliverability_guard/providers/base.py +199 -0
  58. deliverability_guard-0.1.0/src/deliverability_guard/providers/dry_run.py +68 -0
  59. deliverability_guard-0.1.0/src/deliverability_guard/providers/instantly.py +187 -0
  60. deliverability_guard-0.1.0/src/deliverability_guard/providers/smartlead.py +207 -0
  61. deliverability_guard-0.1.0/src/deliverability_guard/py.typed +0 -0
  62. deliverability_guard-0.1.0/src/deliverability_guard/signals/__init__.py +2 -0
  63. deliverability_guard-0.1.0/src/deliverability_guard/signals/postmaster.py +437 -0
  64. deliverability_guard-0.1.0/tests/audit/.gitkeep +0 -0
  65. deliverability_guard-0.1.0/tests/fixtures/fake_driver.py +70 -0
  66. deliverability_guard-0.1.0/tests/fixtures/http.py +74 -0
  67. deliverability_guard-0.1.0/tests/fixtures/instantly/README.md +24 -0
  68. deliverability_guard-0.1.0/tests/fixtures/instantly/analytics_daily_200.json +23 -0
  69. deliverability_guard-0.1.0/tests/fixtures/instantly/analytics_daily_malformed.json +9 -0
  70. deliverability_guard-0.1.0/tests/fixtures/instantly/pause_account_200.json +5 -0
  71. deliverability_guard-0.1.0/tests/fixtures/instantly/rate_limited_429.json +4 -0
  72. deliverability_guard-0.1.0/tests/fixtures/postmaster/README.md +31 -0
  73. deliverability_guard-0.1.0/tests/fixtures/postmaster/compliance_status_compliant.json +18 -0
  74. deliverability_guard-0.1.0/tests/fixtures/postmaster/compliance_status_missing_verdict.json +12 -0
  75. deliverability_guard-0.1.0/tests/fixtures/postmaster/compliance_status_needs_work.json +22 -0
  76. deliverability_guard-0.1.0/tests/fixtures/postmaster/domain_stats_query_200.json +28 -0
  77. deliverability_guard-0.1.0/tests/fixtures/postmaster/domain_stats_query_malformed.json +9 -0
  78. deliverability_guard-0.1.0/tests/fixtures/postmaster/domain_stats_query_paginated_page1.json +11 -0
  79. deliverability_guard-0.1.0/tests/fixtures/postmaster/domain_stats_query_paginated_page2.json +10 -0
  80. deliverability_guard-0.1.0/tests/fixtures/postmaster/rate_limited_429.json +7 -0
  81. deliverability_guard-0.1.0/tests/fixtures/postmaster/verification_token_200.json +5 -0
  82. deliverability_guard-0.1.0/tests/fixtures/smartlead/README.md +15 -0
  83. deliverability_guard-0.1.0/tests/fixtures/smartlead/campaign_statistics_200.json +23 -0
  84. deliverability_guard-0.1.0/tests/fixtures/smartlead/campaign_statistics_malformed.json +9 -0
  85. deliverability_guard-0.1.0/tests/fixtures/smartlead/campaign_status_200.json +4 -0
  86. deliverability_guard-0.1.0/tests/fixtures/smartlead/email_account_update_200.json +4 -0
  87. deliverability_guard-0.1.0/tests/fixtures/smartlead/rate_limited_429.json +3 -0
  88. deliverability_guard-0.1.0/tests/fixtures/streams.py +37 -0
  89. deliverability_guard-0.1.0/tests/identity/.gitkeep +0 -0
  90. deliverability_guard-0.1.0/tests/loops/.gitkeep +0 -0
  91. deliverability_guard-0.1.0/tests/providers/.gitkeep +0 -0
  92. deliverability_guard-0.1.0/tests/signals/.gitkeep +0 -0
  93. deliverability_guard-0.1.0/tests/test_audit_log.py +248 -0
  94. deliverability_guard-0.1.0/tests/test_breaker.py +518 -0
  95. deliverability_guard-0.1.0/tests/test_changepoint.py +102 -0
  96. deliverability_guard-0.1.0/tests/test_dry_run.py +67 -0
  97. deliverability_guard-0.1.0/tests/test_fast_loop.py +118 -0
  98. deliverability_guard-0.1.0/tests/test_feedback_id.py +87 -0
  99. deliverability_guard-0.1.0/tests/test_instantly.py +215 -0
  100. deliverability_guard-0.1.0/tests/test_package.py +7 -0
  101. deliverability_guard-0.1.0/tests/test_parsing.py +80 -0
  102. deliverability_guard-0.1.0/tests/test_posterior.py +233 -0
  103. deliverability_guard-0.1.0/tests/test_postmaster.py +630 -0
  104. deliverability_guard-0.1.0/tests/test_provider_base.py +80 -0
  105. deliverability_guard-0.1.0/tests/test_retry.py +95 -0
  106. deliverability_guard-0.1.0/tests/test_slow_loop.py +123 -0
  107. deliverability_guard-0.1.0/tests/test_smartlead.py +201 -0
  108. deliverability_guard-0.1.0/tests/test_state.py +110 -0
  109. deliverability_guard-0.1.0/tests/test_subdomain_advisor.py +74 -0
  110. deliverability_guard-0.1.0/uv.lock +696 -0
@@ -0,0 +1,21 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+ indent_size = 4
10
+
11
+ [*.py]
12
+ indent_size = 4
13
+
14
+ [*.{yml,yaml,json,toml}]
15
+ indent_size = 2
16
+
17
+ [*.md]
18
+ trim_trailing_whitespace = false
19
+
20
+ [Makefile]
21
+ indent_style = tab
@@ -0,0 +1,5 @@
1
+ # Copy to .env and fill in. Never commit .env — it is gitignored.
2
+ #
3
+ # Instantly v2 API key. Needs scopes: accounts:update, accounts:all
4
+ # (or all:update / all:all). See https://developer.instantly.ai
5
+ INSTANTLY_API_KEY=
@@ -0,0 +1,8 @@
1
+ * text=auto eol=lf
2
+
3
+ *.png binary
4
+ *.jpg binary
5
+ *.jpeg binary
6
+ *.gif binary
7
+ *.ico binary
8
+ *.pdf binary
@@ -0,0 +1,58 @@
1
+ name: Bug report
2
+ description: Something is broken or behaving incorrectly.
3
+ labels: ["bug"]
4
+ body:
5
+ - type: textarea
6
+ id: what-happened
7
+ attributes:
8
+ label: What happened?
9
+ description: A clear description of the bug, including what you expected instead.
10
+ validations:
11
+ required: true
12
+
13
+ - type: textarea
14
+ id: repro
15
+ attributes:
16
+ label: How to reproduce
17
+ description: Minimal steps to reproduce, ideally including your `thresholds.yml` (redact anything sensitive) and whether `dry_run` was on.
18
+ validations:
19
+ required: true
20
+
21
+ - type: input
22
+ id: version
23
+ attributes:
24
+ label: deliverability-guard version
25
+ placeholder: e.g. 0.1.0
26
+ validations:
27
+ required: true
28
+
29
+ - type: input
30
+ id: python-version
31
+ attributes:
32
+ label: Python version
33
+ placeholder: e.g. 3.12.4
34
+ validations:
35
+ required: true
36
+
37
+ - type: dropdown
38
+ id: provider
39
+ attributes:
40
+ label: Provider(s) involved, if relevant
41
+ multiple: true
42
+ options:
43
+ - Instantly
44
+ - Smartlead
45
+ - Google Postmaster Tools
46
+ - Not provider-specific
47
+ - Other (mention in description)
48
+ validations:
49
+ required: false
50
+
51
+ - type: textarea
52
+ id: logs
53
+ attributes:
54
+ label: Relevant decision log / traceback
55
+ description: Paste the relevant excerpt from `audit/log.py` output or a stack trace. This will be rendered as a code block — do not paste real API keys or PII.
56
+ render: shell
57
+ validations:
58
+ required: false
@@ -0,0 +1 @@
1
+ blank_issues_enabled: false
@@ -0,0 +1,29 @@
1
+ name: Feature request
2
+ description: Propose something new — a provider driver, a signal, a capability.
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What problem does this solve?
9
+ description: What can't you do today that this would enable? Prefer a concrete scenario over an abstract capability.
10
+ validations:
11
+ required: true
12
+
13
+ - type: textarea
14
+ id: proposal
15
+ attributes:
16
+ label: Proposed approach
17
+ description: If you have one. It's fine to leave this blank and just describe the problem.
18
+ validations:
19
+ required: false
20
+
21
+ - type: checkboxes
22
+ id: checks
23
+ attributes:
24
+ label: Before submitting
25
+ options:
26
+ - label: I checked BUILD-PLAN.md to see if this is already scoped for a future version, deferred, or explicitly a non-goal.
27
+ required: true
28
+ - label: If this involves a new provider or external API, I've noted what's verified vs. assumed about its behavior.
29
+ required: false
@@ -0,0 +1,23 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ groups:
8
+ python-dependencies:
9
+ patterns:
10
+ - "*"
11
+ cooldown:
12
+ default-days: 7
13
+
14
+ - package-ecosystem: "github-actions"
15
+ directory: "/"
16
+ schedule:
17
+ interval: "weekly"
18
+ groups:
19
+ github-actions:
20
+ patterns:
21
+ - "*"
22
+ cooldown:
23
+ default-days: 7
@@ -0,0 +1,25 @@
1
+ ## What and why
2
+
3
+ <!-- What does this change, and what problem does it solve? -->
4
+
5
+ ## AI disclosure
6
+
7
+ <!-- Required. See CONTRIBUTING.md § Use of AI. -->
8
+
9
+ - AI-assisted: <!-- yes/no -->
10
+ - Model / harness: <!-- e.g. Claude Sonnet 5 via Claude Code -->
11
+ - What was and wasn't AI-generated:
12
+
13
+ ## New dependencies
14
+
15
+ <!-- One line of justification per new dependency, or "None". -->
16
+
17
+ ## Checklist
18
+
19
+ - [ ] `uv run ruff check . && uv run ruff format --check . && uv run pyright && uv run pytest` passes locally
20
+ - [ ] `CHANGELOG.md` has an entry under `## Unreleased`
21
+ - [ ] New behavior has a test, or this PR explains why not
22
+ - [ ] An ADR is included if this decision is expensive to reverse
23
+ - [ ] No code path can pause/throttle a real mailbox without explicit `dry_run=False`
24
+ - [ ] No missing/absent data is coerced to zero anywhere in this change
25
+ - [ ] I reviewed the full diff myself before opening this PR
@@ -0,0 +1,47 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_call:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ # A real Python version matrix. This was a single job while the repo was
14
+ # private, since Actions minutes are metered there; the repo is public
15
+ # now, so there's real value in catching a version-specific regression
16
+ # instead of only ever testing 3.12. See docs/plans/going-public-checklist.md.
17
+ check:
18
+ name: lint, format, types, tests
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ python-version: ["3.12", "3.13"]
24
+ steps:
25
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26
+ with:
27
+ persist-credentials: false
28
+
29
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
30
+ with:
31
+ enable-cache: true
32
+ python-version: ${{ matrix.python-version }}
33
+
34
+ - name: Install dependencies
35
+ run: uv sync --locked --all-groups
36
+
37
+ - name: Ruff check
38
+ run: uv run ruff check .
39
+
40
+ - name: Ruff format check
41
+ run: uv run ruff format --check .
42
+
43
+ - name: Pyright
44
+ run: uv run pyright
45
+
46
+ - name: Pytest
47
+ run: uv run pytest
@@ -0,0 +1,133 @@
1
+ name: Release
2
+
3
+ # Tag-triggered, on the parsedmarc pattern: verify the tag matches the
4
+ # package version -> verify CHANGELOG.md has a section for it -> run the
5
+ # full CI suite -> build -> publish to PyPI via Trusted Publishing (OIDC,
6
+ # no API token secret anywhere) into an environment that requires manual
7
+ # approval -> create the GitHub Release from the CHANGELOG section.
8
+ #
9
+ # Set up the PyPI Trusted Publisher BEFORE pushing the first tag -- PyPI
10
+ # needs to already trust this repo/workflow/environment combination, or the
11
+ # publish step has nothing to authenticate against.
12
+
13
+ on:
14
+ push:
15
+ tags:
16
+ - "v*.*.*"
17
+
18
+ permissions:
19
+ contents: read
20
+
21
+ jobs:
22
+ verify:
23
+ name: verify tag matches version, CHANGELOG has a section
24
+ runs-on: ubuntu-latest
25
+ permissions:
26
+ contents: read
27
+ steps:
28
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
29
+ with:
30
+ persist-credentials: false
31
+
32
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
33
+ with:
34
+ # This workflow publishes release artifacts; a cache poisoned by
35
+ # an earlier run must never be trusted here, so caching is off
36
+ # for this workflow specifically (unlike ci.yml, which runs far
37
+ # more often and has more to gain from it).
38
+ enable-cache: false
39
+
40
+ - name: Verify tag matches package version
41
+ run: |
42
+ tag_version="${GITHUB_REF_NAME#v}"
43
+ pkg_version="$(uvx hatch version)"
44
+ if [ "$tag_version" != "$pkg_version" ]; then
45
+ echo "::error::tag $GITHUB_REF_NAME (version $tag_version) does not match" \
46
+ "package version $pkg_version (src/deliverability_guard/__init__.py)"
47
+ exit 1
48
+ fi
49
+ echo "OK: tag $GITHUB_REF_NAME matches package version $pkg_version"
50
+
51
+ - name: Verify CHANGELOG.md has a section for this version
52
+ run: |
53
+ pkg_version="$(uvx hatch version)"
54
+ if ! grep -qE "^## \[?${pkg_version//./\\.}\]?" CHANGELOG.md; then
55
+ echo "::error::CHANGELOG.md has no '## $pkg_version' section"
56
+ exit 1
57
+ fi
58
+ echo "OK: CHANGELOG.md has a section for $pkg_version"
59
+
60
+ ci:
61
+ name: run CI
62
+ needs: verify
63
+ uses: ./.github/workflows/ci.yml
64
+
65
+ build:
66
+ name: build sdist and wheel
67
+ needs: ci
68
+ runs-on: ubuntu-latest
69
+ permissions:
70
+ contents: read
71
+ steps:
72
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
73
+ with:
74
+ persist-credentials: false
75
+
76
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
77
+ with:
78
+ enable-cache: false # see the same note in the verify job above
79
+
80
+ - name: Build
81
+ run: uv build
82
+
83
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
84
+ with:
85
+ name: dist
86
+ path: dist/
87
+
88
+ publish:
89
+ name: publish to PyPI
90
+ needs: build
91
+ runs-on: ubuntu-latest
92
+ environment:
93
+ name: release
94
+ url: https://pypi.org/project/deliverability-guard/
95
+ permissions:
96
+ id-token: write # required for PyPI Trusted Publishing (OIDC); no API token anywhere
97
+ contents: read
98
+ steps:
99
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
100
+ with:
101
+ name: dist
102
+ path: dist/
103
+
104
+ # PEP 740 attestations are generated and uploaded automatically here
105
+ # under Trusted Publishing -- no extra configuration -- and show up
106
+ # as a provenance badge on the PyPI project page.
107
+ - name: Publish to PyPI
108
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
109
+
110
+ github-release:
111
+ name: create GitHub Release
112
+ needs: publish
113
+ runs-on: ubuntu-latest
114
+ permissions:
115
+ contents: write
116
+ steps:
117
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
118
+ with:
119
+ persist-credentials: false
120
+
121
+ - name: Extract this version's CHANGELOG section
122
+ run: |
123
+ pkg_version="$(sed -n 's/^__version__ = "\(.*\)"/\1/p' src/deliverability_guard/__init__.py)"
124
+ awk -v ver="$pkg_version" '
125
+ $0 ~ "^## \\[?" ver { found=1; next }
126
+ found && /^## / { exit }
127
+ found { print }
128
+ ' CHANGELOG.md > release-notes.md
129
+
130
+ - name: Create GitHub Release
131
+ env:
132
+ GH_TOKEN: ${{ github.token }}
133
+ run: gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes-file release-notes.md
@@ -0,0 +1,39 @@
1
+ name: zizmor
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: ["**"]
8
+
9
+ jobs:
10
+ zizmor:
11
+ name: zizmor GitHub Actions security audit
12
+ runs-on: ubuntu-latest
13
+ permissions:
14
+ contents: read
15
+ security-events: write
16
+ actions: read
17
+ steps:
18
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
+ with:
20
+ persist-credentials: false
21
+
22
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
23
+
24
+ - name: Run zizmor
25
+ run: uvx zizmor --format sarif . > results.sarif
26
+ env:
27
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28
+
29
+ - name: Upload SARIF file
30
+ # Code scanning (and therefore SARIF upload) requires GitHub Advanced
31
+ # Security, which is not enabled by default on a private repo. This
32
+ # step will start succeeding once code scanning is turned on at
33
+ # go-public time (docs/plans/going-public-checklist.md) — until then,
34
+ # don't let an environment limitation fail CI on every push.
35
+ continue-on-error: true
36
+ uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
37
+ with:
38
+ sarif_file: results.sarif
39
+ category: zizmor
@@ -0,0 +1,40 @@
1
+ # Secrets — never commit
2
+ .env
3
+ *.sqlite
4
+ *.sqlite3
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+ *.so
11
+ .Python
12
+ build/
13
+ dist/
14
+ *.egg-info/
15
+ .eggs/
16
+
17
+ # Virtual environments
18
+ .venv/
19
+ venv/
20
+ env/
21
+
22
+ # Testing / coverage
23
+ .pytest_cache/
24
+ .coverage
25
+ .coverage.*
26
+ htmlcov/
27
+ .hypothesis/
28
+
29
+ # Type checkers
30
+ .mypy_cache/
31
+ .pyright/
32
+ .pytype/
33
+
34
+ # Tooling
35
+ .ruff_cache/
36
+
37
+ # Editors / OS
38
+ .DS_Store
39
+ .idea/
40
+ *.swp
@@ -0,0 +1,16 @@
1
+ # Fast, local checks only. Types (pyright) and tests (pytest) run in CI, not
2
+ # here — a slow pre-commit gets disabled. See AGENTS.md.
3
+ repos:
4
+ - repo: https://github.com/astral-sh/ruff-pre-commit
5
+ rev: v0.16.5
6
+ hooks:
7
+ - id: ruff-check
8
+ args: [--fix]
9
+ - id: ruff-format
10
+
11
+ - repo: https://github.com/pre-commit/pre-commit-hooks
12
+ rev: v6.0.0
13
+ hooks:
14
+ - id: end-of-file-fixer
15
+ - id: trailing-whitespace
16
+ - id: check-yaml
@@ -0,0 +1,3 @@
1
+ {
2
+ "recommendations": ["charliermarsh.ruff"]
3
+ }
@@ -0,0 +1,67 @@
1
+ # AGENTS.md
2
+
3
+ ## Stop and read this before you write code
4
+
5
+ This repo has conventions. Violating them wastes a review cycle.
6
+
7
+ ## Commands
8
+
9
+ - Install: `uv sync`
10
+ - Test: `uv run pytest`
11
+ - Lint: `uv run ruff check . && uv run ruff format --check .`
12
+ - Types: `uv run pyright`
13
+ - All of it: `uv run ruff check . && uv run ruff format --check . && uv run pyright && uv run pytest`
14
+
15
+ ## Layout
16
+
17
+ - `src/deliverability_guard/` — the package. `tests/` mirrors it. Never put tests inside the package.
18
+ - `docs/decisions/` — ADRs (MADR 4). Permanent, numbered, never renumbered.
19
+ - `docs/plans/` — dated design plans. Disposable once executed.
20
+ - `config/thresholds.example.yml` — the default ladder. Copy to `thresholds.yml` to use.
21
+
22
+ ## Non-negotiable
23
+
24
+ 1. **Never commit directly to `main`.** Branch, commit, open a PR. Even for a typo.
25
+ 2. **Tests before implementation.** If you are adding behavior, the failing test comes first.
26
+ 3. **No secrets in the repo, ever** — not in tests, not in fixtures, not in examples. Use env vars and `.env.example`.
27
+ 4. **Never re-type a file's contents from tool output.** Output can be truncated. Edit in place.
28
+ 5. **Every dependency added needs a one-line justification in the PR body.**
29
+ 6. **If a decision is expensive to reverse, write an ADR in the same PR.**
30
+ 7. **Linter versions are pinned exactly.** Do not float them to fix a failure — fix the code, or bump deliberately in its own PR.
31
+
32
+ ## Project-specific non-negotiables
33
+
34
+ 1. **DRY RUN IS THE DEFAULT.** Any code path that can pause or throttle a real
35
+ mailbox must be off unless explicitly enabled. A tool that stops someone's
36
+ revenue by accident is worse than no tool. Dry-run must produce decisions
37
+ identical to the live path — implement it as a no-op provider decorator,
38
+ never as a separate logic branch.
39
+ 2. **NEVER coerce missing data to zero.** Absence of complaint data is
40
+ `INSUFFICIENT_DATA`, never `OK`. This is the bug that makes monitoring go
41
+ dark exactly when things are worst — a throttled domain sends less, can
42
+ drop below a provider's privacy threshold, and disappear from reporting
43
+ entirely.
44
+ 3. **Never claim a statistical property the data cannot support.** Under
45
+ roughly 1,000 sends/day/provider this is a leading-indicator monitor, not a
46
+ statistically valid complaint-rate breaker. Say so in the README and never
47
+ soften it.
48
+ 4. **No live API calls in tests.** Recorded fixtures only.
49
+
50
+ ## Before opening a PR
51
+
52
+ - [ ] The full check command above passes locally
53
+ - [ ] `CHANGELOG.md` has an entry under `## Unreleased`
54
+ - [ ] No new file lacks a test, or the PR says why
55
+ - [ ] The PR body discloses: model, harness, and that it was AI-assisted
56
+ - [ ] You showed the human the full diff and got approval
57
+
58
+ ## What gets rejected
59
+
60
+ - Direct commits to `main`
61
+ - Reformatting unrelated code
62
+ - New dependencies without justification
63
+ - "Improvements" nobody asked for, bundled into an unrelated PR
64
+ - Removing a test to make CI pass
65
+ - Anything that makes an approval gate optional
66
+ - A breaker/provider code path that can act (pause/throttle) without an explicit `dry_run=False`
67
+ - Code that treats missing data as zero, anywhere in the pipeline