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.
- deliverability_guard-0.1.0/.editorconfig +21 -0
- deliverability_guard-0.1.0/.env.example +5 -0
- deliverability_guard-0.1.0/.gitattributes +8 -0
- deliverability_guard-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +58 -0
- deliverability_guard-0.1.0/.github/ISSUE_TEMPLATE/config.yml +1 -0
- deliverability_guard-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +29 -0
- deliverability_guard-0.1.0/.github/dependabot.yml +23 -0
- deliverability_guard-0.1.0/.github/pull_request_template.md +25 -0
- deliverability_guard-0.1.0/.github/workflows/ci.yml +47 -0
- deliverability_guard-0.1.0/.github/workflows/release.yml +133 -0
- deliverability_guard-0.1.0/.github/workflows/zizmor.yml +39 -0
- deliverability_guard-0.1.0/.gitignore +40 -0
- deliverability_guard-0.1.0/.pre-commit-config.yaml +16 -0
- deliverability_guard-0.1.0/.vscode/extensions.json +3 -0
- deliverability_guard-0.1.0/AGENTS.md +67 -0
- deliverability_guard-0.1.0/BUILD-PLAN.md +406 -0
- deliverability_guard-0.1.0/CHANGELOG.md +98 -0
- deliverability_guard-0.1.0/CLAUDE.md +1 -0
- deliverability_guard-0.1.0/CODE_OF_CONDUCT.md +50 -0
- deliverability_guard-0.1.0/CONTRIBUTING.md +53 -0
- deliverability_guard-0.1.0/LICENSE +21 -0
- deliverability_guard-0.1.0/PKG-INFO +153 -0
- deliverability_guard-0.1.0/README.md +131 -0
- deliverability_guard-0.1.0/SECURITY.md +25 -0
- deliverability_guard-0.1.0/config/thresholds.example.yml +48 -0
- deliverability_guard-0.1.0/docs/decisions/0000-template.md +78 -0
- deliverability_guard-0.1.0/docs/decisions/0001-record-architecture-decisions.md +90 -0
- deliverability_guard-0.1.0/docs/decisions/0002-beta-binomial-hierarchical-pooling.md +155 -0
- deliverability_guard-0.1.0/docs/decisions/0003-never-auto-resume-after-pause.md +126 -0
- deliverability_guard-0.1.0/docs/demo.gif +0 -0
- deliverability_guard-0.1.0/docs/limits.md +86 -0
- deliverability_guard-0.1.0/docs/plans/going-public-checklist.md +46 -0
- deliverability_guard-0.1.0/docs/postmaster-verdicts.md +204 -0
- deliverability_guard-0.1.0/docs/statistics.md +173 -0
- deliverability_guard-0.1.0/docs/threat-model.md +57 -0
- deliverability_guard-0.1.0/examples/demo.py +93 -0
- deliverability_guard-0.1.0/examples/demo.tape +14 -0
- deliverability_guard-0.1.0/pyproject.toml +110 -0
- deliverability_guard-0.1.0/src/deliverability_guard/__init__.py +7 -0
- deliverability_guard-0.1.0/src/deliverability_guard/audit/__init__.py +1 -0
- deliverability_guard-0.1.0/src/deliverability_guard/audit/log.py +228 -0
- deliverability_guard-0.1.0/src/deliverability_guard/cli.py +7 -0
- deliverability_guard-0.1.0/src/deliverability_guard/engine/__init__.py +1 -0
- deliverability_guard-0.1.0/src/deliverability_guard/engine/breaker.py +365 -0
- deliverability_guard-0.1.0/src/deliverability_guard/engine/changepoint.py +79 -0
- deliverability_guard-0.1.0/src/deliverability_guard/engine/posterior.py +208 -0
- deliverability_guard-0.1.0/src/deliverability_guard/engine/state.py +98 -0
- deliverability_guard-0.1.0/src/deliverability_guard/identity/__init__.py +1 -0
- deliverability_guard-0.1.0/src/deliverability_guard/identity/feedback_id.py +108 -0
- deliverability_guard-0.1.0/src/deliverability_guard/identity/subdomain_advisor.py +96 -0
- deliverability_guard-0.1.0/src/deliverability_guard/loops/__init__.py +1 -0
- deliverability_guard-0.1.0/src/deliverability_guard/loops/fast.py +79 -0
- deliverability_guard-0.1.0/src/deliverability_guard/loops/slow.py +112 -0
- deliverability_guard-0.1.0/src/deliverability_guard/providers/__init__.py +1 -0
- deliverability_guard-0.1.0/src/deliverability_guard/providers/_parsing.py +78 -0
- deliverability_guard-0.1.0/src/deliverability_guard/providers/_retry.py +63 -0
- deliverability_guard-0.1.0/src/deliverability_guard/providers/base.py +199 -0
- deliverability_guard-0.1.0/src/deliverability_guard/providers/dry_run.py +68 -0
- deliverability_guard-0.1.0/src/deliverability_guard/providers/instantly.py +187 -0
- deliverability_guard-0.1.0/src/deliverability_guard/providers/smartlead.py +207 -0
- deliverability_guard-0.1.0/src/deliverability_guard/py.typed +0 -0
- deliverability_guard-0.1.0/src/deliverability_guard/signals/__init__.py +2 -0
- deliverability_guard-0.1.0/src/deliverability_guard/signals/postmaster.py +437 -0
- deliverability_guard-0.1.0/tests/audit/.gitkeep +0 -0
- deliverability_guard-0.1.0/tests/fixtures/fake_driver.py +70 -0
- deliverability_guard-0.1.0/tests/fixtures/http.py +74 -0
- deliverability_guard-0.1.0/tests/fixtures/instantly/README.md +24 -0
- deliverability_guard-0.1.0/tests/fixtures/instantly/analytics_daily_200.json +23 -0
- deliverability_guard-0.1.0/tests/fixtures/instantly/analytics_daily_malformed.json +9 -0
- deliverability_guard-0.1.0/tests/fixtures/instantly/pause_account_200.json +5 -0
- deliverability_guard-0.1.0/tests/fixtures/instantly/rate_limited_429.json +4 -0
- deliverability_guard-0.1.0/tests/fixtures/postmaster/README.md +31 -0
- deliverability_guard-0.1.0/tests/fixtures/postmaster/compliance_status_compliant.json +18 -0
- deliverability_guard-0.1.0/tests/fixtures/postmaster/compliance_status_missing_verdict.json +12 -0
- deliverability_guard-0.1.0/tests/fixtures/postmaster/compliance_status_needs_work.json +22 -0
- deliverability_guard-0.1.0/tests/fixtures/postmaster/domain_stats_query_200.json +28 -0
- deliverability_guard-0.1.0/tests/fixtures/postmaster/domain_stats_query_malformed.json +9 -0
- deliverability_guard-0.1.0/tests/fixtures/postmaster/domain_stats_query_paginated_page1.json +11 -0
- deliverability_guard-0.1.0/tests/fixtures/postmaster/domain_stats_query_paginated_page2.json +10 -0
- deliverability_guard-0.1.0/tests/fixtures/postmaster/rate_limited_429.json +7 -0
- deliverability_guard-0.1.0/tests/fixtures/postmaster/verification_token_200.json +5 -0
- deliverability_guard-0.1.0/tests/fixtures/smartlead/README.md +15 -0
- deliverability_guard-0.1.0/tests/fixtures/smartlead/campaign_statistics_200.json +23 -0
- deliverability_guard-0.1.0/tests/fixtures/smartlead/campaign_statistics_malformed.json +9 -0
- deliverability_guard-0.1.0/tests/fixtures/smartlead/campaign_status_200.json +4 -0
- deliverability_guard-0.1.0/tests/fixtures/smartlead/email_account_update_200.json +4 -0
- deliverability_guard-0.1.0/tests/fixtures/smartlead/rate_limited_429.json +3 -0
- deliverability_guard-0.1.0/tests/fixtures/streams.py +37 -0
- deliverability_guard-0.1.0/tests/identity/.gitkeep +0 -0
- deliverability_guard-0.1.0/tests/loops/.gitkeep +0 -0
- deliverability_guard-0.1.0/tests/providers/.gitkeep +0 -0
- deliverability_guard-0.1.0/tests/signals/.gitkeep +0 -0
- deliverability_guard-0.1.0/tests/test_audit_log.py +248 -0
- deliverability_guard-0.1.0/tests/test_breaker.py +518 -0
- deliverability_guard-0.1.0/tests/test_changepoint.py +102 -0
- deliverability_guard-0.1.0/tests/test_dry_run.py +67 -0
- deliverability_guard-0.1.0/tests/test_fast_loop.py +118 -0
- deliverability_guard-0.1.0/tests/test_feedback_id.py +87 -0
- deliverability_guard-0.1.0/tests/test_instantly.py +215 -0
- deliverability_guard-0.1.0/tests/test_package.py +7 -0
- deliverability_guard-0.1.0/tests/test_parsing.py +80 -0
- deliverability_guard-0.1.0/tests/test_posterior.py +233 -0
- deliverability_guard-0.1.0/tests/test_postmaster.py +630 -0
- deliverability_guard-0.1.0/tests/test_provider_base.py +80 -0
- deliverability_guard-0.1.0/tests/test_retry.py +95 -0
- deliverability_guard-0.1.0/tests/test_slow_loop.py +123 -0
- deliverability_guard-0.1.0/tests/test_smartlead.py +201 -0
- deliverability_guard-0.1.0/tests/test_state.py +110 -0
- deliverability_guard-0.1.0/tests/test_subdomain_advisor.py +74 -0
- 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,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,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
|