rust-py-audit 0.3.2__tar.gz → 0.3.3__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.
- rust_py_audit-0.3.3/.github/workflows/release.yml +328 -0
- rust_py_audit-0.3.3/CHANGELOG.md +160 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/Cargo.lock +1 -1
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/Cargo.toml +1 -1
- rust_py_audit-0.3.3/Makefile +68 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/PKG-INFO +2 -3
- rust_py_audit-0.3.3/clippy.toml +11 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/pyproject.toml +9 -4
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/python/rust_py_audit/__init__.py +1 -1
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/src/audit_logger.rs +30 -11
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/src/errors.rs +1 -3
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/src/event.rs +2 -1
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/src/hash.rs +2 -2
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/src/immutablelog_client.rs +88 -35
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/src/retry.rs +24 -7
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/src/storage.rs +31 -10
- rust_py_audit-0.3.2/.github/workflows/release.yml +0 -208
- rust_py_audit-0.3.2/CHANGELOG.md +0 -50
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/.gitignore +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/LICENSE +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/README.md +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/ROADMAP.md +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/examples/__init__.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/examples/basic_usage.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/examples/django_example/__init__.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/examples/django_example/settings.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/examples/django_example/urls.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/examples/django_example/views.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/examples/fastapi_app.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/examples/immutablelog_basic.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/python/rust_py_audit/django.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/python/rust_py_audit/fastapi.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/src/immutablelog_config.rs +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/src/immutablelog_receipt.rs +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/src/lib.rs +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/src/pyconv.rs +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/src/verifier.rs +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/tests/conftest.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/tests/test_audit_logger.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/tests/test_concurrency.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/tests/test_django_immutablelog.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/tests/test_django_middleware.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/tests/test_fastapi_immutablelog.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/tests/test_fastapi_middleware.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/tests/test_flush_pending.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/tests/test_hello.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/tests/test_immutablelog_hybrid.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/tests/test_immutablelog_remote.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/tests/test_metadata_bigint.py +0 -0
- {rust_py_audit-0.3.2 → rust_py_audit-0.3.3}/tests/test_verify.py +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
name: CI / Release
|
|
2
|
+
|
|
3
|
+
# Push e PR rodam `lint` + `test` e param por aí. Os jobs de build são
|
|
4
|
+
# liberados por uma tag `v*` ou por um `workflow_dispatch` manual — o segundo
|
|
5
|
+
# existe para o ensaio no TestPyPI, abaixo.
|
|
6
|
+
#
|
|
7
|
+
# **Só a tag publica no PyPI de produção.** O job `release` é o único guardado
|
|
8
|
+
# apenas por `startsWith(github.ref, 'refs/tags/')`, sem a cláusula de
|
|
9
|
+
# dispatch: um disparo manual constrói tudo e vai para o TestPyPI, nunca para
|
|
10
|
+
# o índice de verdade. Publicação no PyPI não se desfaz, então essa assimetria
|
|
11
|
+
# é proposital.
|
|
12
|
+
on:
|
|
13
|
+
push:
|
|
14
|
+
branches: [main]
|
|
15
|
+
tags:
|
|
16
|
+
- "v*"
|
|
17
|
+
pull_request:
|
|
18
|
+
workflow_dispatch: {}
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
# O lint faz parte do portão de release, não de um workflow paralelo.
|
|
25
|
+
# Nos repositórios de workflow único, um `lint` vermelho ao lado de uma
|
|
26
|
+
# publicação bem-sucedida seria ruído difícil de ler — a leitura passa a ser
|
|
27
|
+
# inequívoca: vermelho significa que não publicou.
|
|
28
|
+
#
|
|
29
|
+
# A toolchain é fixa, não "stable": com `-D warnings`, um clippy mais novo
|
|
30
|
+
# no runner reprova código que ninguém tocou, e a CI ficaria vermelha por
|
|
31
|
+
# conta do calendário. Subir este número vira uma mudança deliberada.
|
|
32
|
+
lint:
|
|
33
|
+
name: fmt + clippy
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
- uses: dtolnay/rust-toolchain@1.88.0
|
|
38
|
+
with:
|
|
39
|
+
components: rustfmt, clippy
|
|
40
|
+
- uses: Swatinem/rust-cache@v2
|
|
41
|
+
- name: cargo fmt
|
|
42
|
+
run: cargo fmt --all -- --check
|
|
43
|
+
# clippy só checa tipos, não linka, então --all-features é seguro aqui
|
|
44
|
+
# (diferente do `cargo test`, que precisa de --no-default-features).
|
|
45
|
+
- name: cargo clippy
|
|
46
|
+
run: cargo clippy --all-targets --all-features -- -D warnings
|
|
47
|
+
|
|
48
|
+
# Nothing gets built, and nothing reaches PyPI, until this passes. Publishing
|
|
49
|
+
# to PyPI is irreversible: a bad wheel cannot be replaced, only yanked.
|
|
50
|
+
test:
|
|
51
|
+
name: Test (Rust + Python)
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v4
|
|
55
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
56
|
+
- uses: actions/setup-python@v5
|
|
57
|
+
with:
|
|
58
|
+
python-version: "3.12"
|
|
59
|
+
|
|
60
|
+
# --no-default-features drops extension-module, which tells the linker to
|
|
61
|
+
# leave libpython symbols undefined. That is correct for the wheel but
|
|
62
|
+
# produces a test binary that cannot link on Linux.
|
|
63
|
+
- name: Rust unit tests
|
|
64
|
+
env:
|
|
65
|
+
PYO3_PYTHON: python
|
|
66
|
+
run: cargo test --no-default-features --lib
|
|
67
|
+
|
|
68
|
+
- name: Build, install the extension and run the Python tests
|
|
69
|
+
# `pip install` drives the maturin build backend, so it compiles the
|
|
70
|
+
# Rust extension and installs it (plus the dev extras) into the
|
|
71
|
+
# runner's Python — no virtualenv needed, unlike `maturin develop`,
|
|
72
|
+
# which aborts when it cannot find one.
|
|
73
|
+
run: |
|
|
74
|
+
python -m pip install --upgrade pip
|
|
75
|
+
python -m pip install ".[dev]"
|
|
76
|
+
python -m pytest
|
|
77
|
+
|
|
78
|
+
linux:
|
|
79
|
+
runs-on: ubuntu-latest
|
|
80
|
+
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
|
81
|
+
needs: [test, lint]
|
|
82
|
+
strategy:
|
|
83
|
+
matrix:
|
|
84
|
+
target: [x86_64, aarch64]
|
|
85
|
+
steps:
|
|
86
|
+
- uses: actions/checkout@v4
|
|
87
|
+
- uses: PyO3/maturin-action@v1
|
|
88
|
+
env:
|
|
89
|
+
# `ring` (puxado por `rustls`, via `reqwest`) falha ao cross-compilar
|
|
90
|
+
# sua assembly pregenerada para aarch64 dentro do container manylinux
|
|
91
|
+
# do maturin-action: o cross-gcc não define `__ARM_ARCH` por padrão
|
|
92
|
+
# nesse ambiente, e o preprocessor de `ring` exige essa macro
|
|
93
|
+
# (erro: "ARM assembler must define __ARM_ARCH"). Forçar a macro via
|
|
94
|
+
# CFLAGS é o workaround padrão para esse problema conhecido — não
|
|
95
|
+
# afeta o build x86_64 (a env var é específica do target aarch64).
|
|
96
|
+
CFLAGS_aarch64_unknown_linux_gnu: -D__ARM_ARCH=8
|
|
97
|
+
with:
|
|
98
|
+
target: ${{ matrix.target }}
|
|
99
|
+
args: --release --out dist --find-interpreter
|
|
100
|
+
manylinux: auto
|
|
101
|
+
- uses: actions/upload-artifact@v4
|
|
102
|
+
with:
|
|
103
|
+
name: wheels-linux-${{ matrix.target }}
|
|
104
|
+
path: dist
|
|
105
|
+
|
|
106
|
+
musllinux:
|
|
107
|
+
runs-on: ubuntu-latest
|
|
108
|
+
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
|
109
|
+
needs: [test, lint]
|
|
110
|
+
strategy:
|
|
111
|
+
matrix:
|
|
112
|
+
target: [x86_64, aarch64]
|
|
113
|
+
steps:
|
|
114
|
+
- uses: actions/checkout@v4
|
|
115
|
+
- uses: PyO3/maturin-action@v1
|
|
116
|
+
with:
|
|
117
|
+
target: ${{ matrix.target }}
|
|
118
|
+
args: --release --out dist --find-interpreter
|
|
119
|
+
manylinux: musllinux_1_2
|
|
120
|
+
- uses: actions/upload-artifact@v4
|
|
121
|
+
with:
|
|
122
|
+
name: wheels-musllinux-${{ matrix.target }}
|
|
123
|
+
path: dist
|
|
124
|
+
|
|
125
|
+
windows:
|
|
126
|
+
runs-on: windows-latest
|
|
127
|
+
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
|
128
|
+
needs: [test, lint]
|
|
129
|
+
steps:
|
|
130
|
+
- uses: actions/checkout@v4
|
|
131
|
+
- uses: actions/setup-python@v5
|
|
132
|
+
with:
|
|
133
|
+
architecture: x64
|
|
134
|
+
- uses: PyO3/maturin-action@v1
|
|
135
|
+
with:
|
|
136
|
+
target: x64
|
|
137
|
+
args: --release --out dist --find-interpreter
|
|
138
|
+
- uses: actions/upload-artifact@v4
|
|
139
|
+
with:
|
|
140
|
+
name: wheels-windows-x64
|
|
141
|
+
path: dist
|
|
142
|
+
|
|
143
|
+
macos:
|
|
144
|
+
runs-on: macos-latest
|
|
145
|
+
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
|
146
|
+
needs: [test, lint]
|
|
147
|
+
strategy:
|
|
148
|
+
matrix:
|
|
149
|
+
target: [x86_64, aarch64]
|
|
150
|
+
steps:
|
|
151
|
+
- uses: actions/checkout@v4
|
|
152
|
+
- uses: PyO3/maturin-action@v1
|
|
153
|
+
with:
|
|
154
|
+
target: ${{ matrix.target }}
|
|
155
|
+
args: --release --out dist --find-interpreter
|
|
156
|
+
- uses: actions/upload-artifact@v4
|
|
157
|
+
with:
|
|
158
|
+
name: wheels-macos-${{ matrix.target }}
|
|
159
|
+
path: dist
|
|
160
|
+
|
|
161
|
+
sdist:
|
|
162
|
+
runs-on: ubuntu-latest
|
|
163
|
+
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
|
164
|
+
needs: [test, lint]
|
|
165
|
+
steps:
|
|
166
|
+
- uses: actions/checkout@v4
|
|
167
|
+
- uses: PyO3/maturin-action@v1
|
|
168
|
+
with:
|
|
169
|
+
command: sdist
|
|
170
|
+
args: --out dist
|
|
171
|
+
- uses: actions/upload-artifact@v4
|
|
172
|
+
with:
|
|
173
|
+
name: wheels-sdist
|
|
174
|
+
path: dist
|
|
175
|
+
|
|
176
|
+
# Ensaio da troca OIDC, sem tocar no índice de produção. É o que permite
|
|
177
|
+
# descobrir um publisher mal cadastrado num lugar onde nada é irreversível —
|
|
178
|
+
# no PyPI, um nome de arquivo publicado nunca pode ser reusado.
|
|
179
|
+
#
|
|
180
|
+
# ⚠️ Precisa do seu próprio publisher, cadastrado no TestPyPI (que é uma
|
|
181
|
+
# conta separada do PyPI), com owner/repo, workflow `release.yml` e
|
|
182
|
+
# environment `testpypi`.
|
|
183
|
+
#
|
|
184
|
+
# Não há verificação pós-upload aqui de propósito: o que se está validando é
|
|
185
|
+
# a troca de token, e o TestPyPI expira distribuições — reprovar por um
|
|
186
|
+
# arquivo que o índice de teste descartou seria alarme falso.
|
|
187
|
+
publish-testpypi:
|
|
188
|
+
name: Publish to TestPyPI (ensaio)
|
|
189
|
+
runs-on: ubuntu-latest
|
|
190
|
+
if: github.event_name == 'workflow_dispatch'
|
|
191
|
+
needs: [lint, test, linux, musllinux, windows, macos, sdist]
|
|
192
|
+
environment: testpypi
|
|
193
|
+
permissions:
|
|
194
|
+
id-token: write
|
|
195
|
+
steps:
|
|
196
|
+
- uses: actions/download-artifact@v4
|
|
197
|
+
with:
|
|
198
|
+
pattern: wheels-*
|
|
199
|
+
path: dist
|
|
200
|
+
merge-multiple: true
|
|
201
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
202
|
+
with:
|
|
203
|
+
repository-url: https://test.pypi.org/legacy/
|
|
204
|
+
packages-dir: dist
|
|
205
|
+
skip-existing: true
|
|
206
|
+
|
|
207
|
+
release:
|
|
208
|
+
name: Publish to PyPI
|
|
209
|
+
runs-on: ubuntu-latest
|
|
210
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
211
|
+
needs: [lint, test, linux, musllinux, windows, macos, sdist]
|
|
212
|
+
|
|
213
|
+
# Trusted publishing (OIDC). Não existe mais `secrets.PYPI_API_TOKEN` aqui:
|
|
214
|
+
# o GitHub emite um JWT curto afirmando repositório, workflow e ref, e o
|
|
215
|
+
# PyPI o troca por um token de upload válido por poucos minutos e só para
|
|
216
|
+
# este projeto. Some o segredo de longa duração que, se vazasse, publicaria
|
|
217
|
+
# até alguém revogá-lo à mão.
|
|
218
|
+
#
|
|
219
|
+
# ⚠️ Os três valores abaixo precisam bater EXATAMENTE com o publisher
|
|
220
|
+
# cadastrado em PyPI → Manage → Publishing:
|
|
221
|
+
# owner/repo .... robertolima-dev/rust-py-audit
|
|
222
|
+
# workflow ...... release.yml (o nome deste arquivo)
|
|
223
|
+
# environment ... pypi
|
|
224
|
+
# Divergência em qualquer um deles é rejeitada na troca do token.
|
|
225
|
+
environment: pypi
|
|
226
|
+
permissions:
|
|
227
|
+
id-token: write
|
|
228
|
+
|
|
229
|
+
steps:
|
|
230
|
+
- uses: actions/download-artifact@v4
|
|
231
|
+
with:
|
|
232
|
+
pattern: wheels-*
|
|
233
|
+
path: dist
|
|
234
|
+
merge-multiple: true
|
|
235
|
+
- uses: actions/setup-python@v5
|
|
236
|
+
with:
|
|
237
|
+
python-version: "3.12"
|
|
238
|
+
# `requests` é só para a verificação pós-upload, mais abaixo. A maturin
|
|
239
|
+
# não é mais instalada aqui: quem publica agora é a action oficial do
|
|
240
|
+
# PyPA, que é a que sabe fazer a troca OIDC.
|
|
241
|
+
- name: Install the verification dependency
|
|
242
|
+
run: python -m pip install --upgrade requests
|
|
243
|
+
|
|
244
|
+
# A sanity check before anything leaves the runner: publishing an
|
|
245
|
+
# incomplete set is worse than not publishing, because PyPI never lets a
|
|
246
|
+
# filename be reused — a half-uploaded version can only be completed,
|
|
247
|
+
# never replaced.
|
|
248
|
+
- name: Verify the artifact set is complete
|
|
249
|
+
run: |
|
|
250
|
+
set -euo pipefail
|
|
251
|
+
ls -la dist/
|
|
252
|
+
test -n "$(find dist -name '*.tar.gz' -print -quit)" \
|
|
253
|
+
|| { echo "::error::sdist ausente — abortando antes de publicar"; exit 1; }
|
|
254
|
+
wheels=$(find dist -name '*.whl' | wc -l)
|
|
255
|
+
echo "wheels: $wheels"
|
|
256
|
+
test "$wheels" -ge 7 \
|
|
257
|
+
|| { echo "::error::só $wheels wheels; esperado >= 7 (2 linux, 2 musl, 1 win, 2 macos)"; exit 1; }
|
|
258
|
+
|
|
259
|
+
# O PyPI devolve 500 transitório com frequência suficiente para importar,
|
|
260
|
+
# e o upload é arquivo por arquivo: uma falha no meio deixa a versão
|
|
261
|
+
# publicada pela metade. `skip-existing` é o que torna a segunda
|
|
262
|
+
# tentativa segura — sem ele, ela morre nos arquivos que já subiram.
|
|
263
|
+
#
|
|
264
|
+
# São dois passos explícitos em vez de um wrapper de retry porque o
|
|
265
|
+
# trusted publishing exige a action oficial, e ela não dá para acionar
|
|
266
|
+
# por linha de comando de dentro do `nick-fields/retry`. Mesma forma que
|
|
267
|
+
# o `rust_py_cache` e o `rust_py_rate_limit` já rodam.
|
|
268
|
+
- name: Publish to PyPI (attempt 1)
|
|
269
|
+
id: publish1
|
|
270
|
+
continue-on-error: true
|
|
271
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
272
|
+
with:
|
|
273
|
+
packages-dir: dist
|
|
274
|
+
skip-existing: true
|
|
275
|
+
|
|
276
|
+
- name: Wait before retrying
|
|
277
|
+
if: steps.publish1.outcome == 'failure'
|
|
278
|
+
run: sleep 45
|
|
279
|
+
|
|
280
|
+
- name: Publish to PyPI (attempt 2)
|
|
281
|
+
if: steps.publish1.outcome == 'failure'
|
|
282
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
283
|
+
with:
|
|
284
|
+
packages-dir: dist
|
|
285
|
+
skip-existing: true
|
|
286
|
+
|
|
287
|
+
# Proves the release is actually complete, rather than trusting an exit
|
|
288
|
+
# code that a partial upload can still produce.
|
|
289
|
+
- name: Verify every file reached PyPI
|
|
290
|
+
run: |
|
|
291
|
+
set -euo pipefail
|
|
292
|
+
python - <<'PY'
|
|
293
|
+
import os, sys, glob, time, requests
|
|
294
|
+
version = os.environ["GITHUB_REF_NAME"].lstrip("v")
|
|
295
|
+
# Só .whl e .tar.gz: são os únicos tipos de arquivo que o PyPI hospeda.
|
|
296
|
+
# `pypa/gh-action-pypi-publish` deixa um .publish.attestation por
|
|
297
|
+
# distribuição em dist/ depois de publicar (atestados PEP 740, que
|
|
298
|
+
# sobem junto do arquivo, não como arquivo próprio). Um glob amplo os
|
|
299
|
+
# conta como "faltando no PyPI" e reprova uma release que deu certo.
|
|
300
|
+
#
|
|
301
|
+
# ⚠️ Isto deixou de ser precaução e passou a valer aqui: com trusted
|
|
302
|
+
# publishing a action gera os atestados, coisa que o `maturin upload`
|
|
303
|
+
# não fazia. O recorte veio do alarme falso da rate_limit 0.2.1
|
|
304
|
+
# (P1-9) e é o que impede esta migração de reprovar o primeiro
|
|
305
|
+
# release por um motivo inventado.
|
|
306
|
+
local = {
|
|
307
|
+
os.path.basename(p)
|
|
308
|
+
for pattern in ("dist/*.whl", "dist/*.tar.gz")
|
|
309
|
+
for p in glob.glob(pattern)
|
|
310
|
+
}
|
|
311
|
+
url = f"https://pypi.org/pypi/rust-py-audit/{version}/json"
|
|
312
|
+
for attempt in range(6): # PyPI's JSON API is cached
|
|
313
|
+
response = requests.get(url, timeout=30)
|
|
314
|
+
if response.status_code == 200:
|
|
315
|
+
remote = {f["filename"] for f in response.json()["urls"]}
|
|
316
|
+
missing = local - remote
|
|
317
|
+
if not missing:
|
|
318
|
+
print(f"OK: {len(remote)} arquivos publicados na {version}")
|
|
319
|
+
sys.exit(0)
|
|
320
|
+
else:
|
|
321
|
+
missing = local
|
|
322
|
+
print(f"tentativa {attempt + 1}: faltam {len(missing)}")
|
|
323
|
+
time.sleep(20)
|
|
324
|
+
print("::error::faltaram arquivos no PyPI:")
|
|
325
|
+
for name in sorted(missing):
|
|
326
|
+
print(f" - {name}")
|
|
327
|
+
sys.exit(1)
|
|
328
|
+
PY
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Todas as mudanças relevantes deste projeto são registradas aqui.
|
|
4
|
+
|
|
5
|
+
O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/),
|
|
6
|
+
e o versionamento segue [SemVer](https://semver.org/lang/pt-BR/).
|
|
7
|
+
|
|
8
|
+
## [0.3.3] — não publicada
|
|
9
|
+
|
|
10
|
+
Correção do passo de verificação pós-upload introduzido na 0.3.2, mais o
|
|
11
|
+
trabalho de licença e lint de 12/08/2026 (**P1-4** e **P1-6**).
|
|
12
|
+
|
|
13
|
+
### Alterado
|
|
14
|
+
|
|
15
|
+
- **Publicação migrada para trusted publishing (OIDC).** O `release.yml` não
|
|
16
|
+
usa mais `secrets.PYPI_API_TOKEN`: o GitHub emite um JWT curto afirmando
|
|
17
|
+
repositório, workflow e ref, e o PyPI o troca por um token de upload válido
|
|
18
|
+
por poucos minutos e restrito a este projeto. Acaba o segredo de longa
|
|
19
|
+
duração que, se vazasse, publicaria até ser revogado à mão.
|
|
20
|
+
|
|
21
|
+
O passo de upload deixou de ser `maturin upload` dentro de um
|
|
22
|
+
`nick-fields/retry` e passou a ser dois `pypa/gh-action-pypi-publish`
|
|
23
|
+
explícitos (`continue-on-error` na primeira, `if: outcome == 'failure'` na
|
|
24
|
+
segunda) — a action oficial é a que faz a troca OIDC, e não dá para acioná-la
|
|
25
|
+
por linha de comando de dentro do wrapper de retry. `skip-existing: true`
|
|
26
|
+
mantém a segunda tentativa idempotente. É a mesma forma que `rust_py_cache` e
|
|
27
|
+
`rust_py_rate_limit` já rodam.
|
|
28
|
+
|
|
29
|
+
⚠️ **Depende de configuração no PyPI**, e os valores têm que bater exatamente:
|
|
30
|
+
`robertolima-dev/rust-py-audit`, workflow `release.yml`, environment `pypi`.
|
|
31
|
+
O modo de falha é seguro — sem o publisher cadastrado, o job falha na troca
|
|
32
|
+
do token, depois de construir tudo e antes de qualquer arquivo sair do
|
|
33
|
+
runner. Nada é publicado pela metade.
|
|
34
|
+
|
|
35
|
+
💡 A partir daqui a release passa a gerar **atestados PEP 740**, que o
|
|
36
|
+
`maturin upload` não gerava. A verificação pós-upload já estava preparada:
|
|
37
|
+
ela só conta `*.whl` e `*.tar.gz` desde a 0.3.2, recorte que nasceu do alarme
|
|
38
|
+
falso da `rate_limit` 0.2.1. Sem ele, este primeiro release por OIDC seria
|
|
39
|
+
reprovado por um motivo inventado.
|
|
40
|
+
|
|
41
|
+
- **Ensaio no TestPyPI por `workflow_dispatch`.** Job `publish-testpypi` novo,
|
|
42
|
+
na forma que o `rust_py_rate_limit` já tem: um disparo manual constrói a
|
|
43
|
+
matriz inteira e publica no índice de teste, para validar a troca de token
|
|
44
|
+
num lugar onde nada é irreversível.
|
|
45
|
+
|
|
46
|
+
Para isso, os cinco jobs de build passaram de
|
|
47
|
+
`if: startsWith(github.ref, 'refs/tags/')` para
|
|
48
|
+
`... || github.event_name == 'workflow_dispatch'` — sem essa mudança um
|
|
49
|
+
disparo manual não construiria wheel nenhuma e o ensaio ficaria sem o que
|
|
50
|
+
publicar.
|
|
51
|
+
|
|
52
|
+
⚠️ **O job `release` deliberadamente NÃO ganhou a cláusula de dispatch.** A
|
|
53
|
+
assimetria é a proteção: disparo manual vai ao TestPyPI, só a tag chega ao
|
|
54
|
+
PyPI de produção.
|
|
55
|
+
|
|
56
|
+
### Adicionado
|
|
57
|
+
|
|
58
|
+
- **Job `lint`** (`cargo fmt --check` + `cargo clippy -D warnings`) no
|
|
59
|
+
`release.yml`, com `needs: [test, lint]` em todos os jobs de build e no de
|
|
60
|
+
publicação — o lint barra a release, igual ao `test`. A toolchain é fixa
|
|
61
|
+
(`dtolnay/rust-toolchain@1.88.0`) e não `stable`: com `-D warnings`, um
|
|
62
|
+
clippy mais novo no runner reprovaria código que ninguém tocou.
|
|
63
|
+
- **`clippy.toml` e `Makefile`** compartilhados com os outros quatro
|
|
64
|
+
`rust_py_*`. O `clippy.toml` sobe `too-many-arguments-threshold` para 10:
|
|
65
|
+
os construtores PyO3 espelham a assinatura Python, e reagrupar em struct
|
|
66
|
+
para agradar o lint pioraria a API que o usuário vê.
|
|
67
|
+
|
|
68
|
+
### Corrigido
|
|
69
|
+
|
|
70
|
+
- **Metadado de licença ausente no PyPI.** A 0.3.2 está publicada com
|
|
71
|
+
`license_expression: null` e `license: ""` — ou seja, sem metadado de
|
|
72
|
+
licença nenhum, só o classifier. A causa raiz não era a forma no
|
|
73
|
+
`pyproject.toml` (que já usava `license = "MIT"`), e sim a **maturin velha**:
|
|
74
|
+
versões anteriores à 1.14 não emitem `License-Expression`. O piso do
|
|
75
|
+
`[build-system]` subiu de `>=1.5` para `>=1.14`, entrou
|
|
76
|
+
`license-files = ["LICENSE"]` e o classifier `License :: OSI Approved` saiu,
|
|
77
|
+
por ser mutuamente exclusivo com `License-Expression` no Metadata 2.4.
|
|
78
|
+
Verificado: o `dist-info` gerado agora traz `License-Expression: MIT` e o
|
|
79
|
+
texto da licença em `licenses/LICENSE`.
|
|
80
|
+
|
|
81
|
+
- **Formatação.** `cargo fmt --all` aplicado — eram 38 blocos divergentes, de
|
|
82
|
+
longe o maior dos cinco. Mudança puramente de formatação, sem efeito em
|
|
83
|
+
comportamento; os 50 testes Rust seguem passando.
|
|
84
|
+
|
|
85
|
+
### Conhecido
|
|
86
|
+
|
|
87
|
+
- `src/audit_logger.rs` ganhou um `#![allow(clippy::useless_conversion)]` de
|
|
88
|
+
módulo. **Não é código a corrigir:** o lint aponta o *tipo de retorno* de
|
|
89
|
+
`log`, `verify` e `flush_pending`, mas a conversão está no código que
|
|
90
|
+
`#[pymethods]` gera — a macro do pyo3 0.22. `#[allow]` no bloco `impl` e na
|
|
91
|
+
função foram testados e não silenciam, porque o código gerado fica fora do
|
|
92
|
+
`impl`. `cache` (0.24), `monitor` (0.25) e `scheduler` (0.29) não emitem o
|
|
93
|
+
aviso, o que confirma ser artefato da versão. O `allow` sai com o **P3-2**.
|
|
94
|
+
|
|
95
|
+
### Corrigido — pipeline (0.3.2)
|
|
96
|
+
|
|
97
|
+
- **Falso negativo na verificação pós-publicação.** O passo comparava
|
|
98
|
+
`glob("dist/*")` com o que estava no PyPI. Nada quebrou aqui — o
|
|
99
|
+
`maturin upload` não deixa arquivo extra em `dist/` —, mas o glob amplo é
|
|
100
|
+
frágil: qualquer arquivo novo no diretório reprovaria uma release que deu
|
|
101
|
+
certo, que foi exatamente o que aconteceu no `rust_py_rate_limit`. O passo
|
|
102
|
+
agora só considera `*.whl` e `*.tar.gz`, os únicos tipos de arquivo que o
|
|
103
|
+
PyPI hospeda.
|
|
104
|
+
|
|
105
|
+
- **Os testes passam a rodar em push e PR.** O `release.yml` é o único workflow
|
|
106
|
+
destes repositórios e só disparava em tag, então o job `test` só rodava na
|
|
107
|
+
hora do release — protegia a publicação, mas só avisava do problema no pior
|
|
108
|
+
momento. Agora o workflow também dispara em `push` para `main` e em
|
|
109
|
+
`pull_request`, e os jobs de build e o de publicação ganharam
|
|
110
|
+
`if: startsWith(github.ref, 'refs/tags/')`. Essa guarda corrige um risco que
|
|
111
|
+
existia antes: o job de release **não tinha `if:`**, então um
|
|
112
|
+
`workflow_dispatch` manual a partir de um branch teria publicado no PyPI.
|
|
113
|
+
|
|
114
|
+
**Não vai ao PyPI sozinha:** a 0.3.2 está lá completa e esta versão só
|
|
115
|
+
mexe no pipeline. O número fica reservado e sai com a próxima mudança que
|
|
116
|
+
valha uma tag.
|
|
117
|
+
|
|
118
|
+
## [0.3.2] - 2026-08-11
|
|
119
|
+
|
|
120
|
+
Release de infraestrutura: nada muda no comportamento da biblioteca.
|
|
121
|
+
|
|
122
|
+
### Corrigido
|
|
123
|
+
|
|
124
|
+
- **A publicação não roda mais sem teste.** O `release.yml` era o único
|
|
125
|
+
workflow do repositório e só tinha jobs de build: `git push --tags`
|
|
126
|
+
publicava no PyPI sem nenhum `cargo test` nem `pytest`. Agora existe um job
|
|
127
|
+
`test` e todos os jobs de wheel, o de sdist e o de release declaram
|
|
128
|
+
`needs: test`. (BACKLOG2 P1-1)
|
|
129
|
+
- **O upload aguenta o 500 do PyPI.** O upload é arquivo por arquivo e sem
|
|
130
|
+
transação — uma falha no meio deixa a versão publicada pela metade, e o PyPI
|
|
131
|
+
nunca permite reusar um nome de arquivo. O passo de publicação agora tem
|
|
132
|
+
retry (4 tentativas), continua usando `--skip-existing` para ser idempotente,
|
|
133
|
+
confere o conjunto de artefatos **antes** de subir qualquer coisa (aborta se
|
|
134
|
+
faltar o sdist) e valida contra a API do PyPI **depois**, listando nome por
|
|
135
|
+
nome o que não chegou. (BACKLOG2 P1-9)
|
|
136
|
+
|
|
137
|
+
### Adicionado
|
|
138
|
+
|
|
139
|
+
- Este `CHANGELOG.md`. (BACKLOG2 P1-5)
|
|
140
|
+
|
|
141
|
+
## [0.3.1] e anteriores
|
|
142
|
+
|
|
143
|
+
Sem changelog retroativo. O histórico do que entrou em cada versão está no
|
|
144
|
+
`ROADMAP.md`, e o das mudanças no `git log`.
|
|
145
|
+
|
|
146
|
+
Resumo do que o projeto entrega hoje:
|
|
147
|
+
|
|
148
|
+
- `AuditLogger` com log append-only em JSONL e cadeia de hashes encadeada.
|
|
149
|
+
- `verify()` para detectar adulteração da cadeia.
|
|
150
|
+
- Integração com o ImmutableLog nos modos remoto e híbrido.
|
|
151
|
+
- Middlewares para FastAPI e Django.
|
|
152
|
+
- Wheels `abi3-py310` — uma wheel por plataforma, válida da 3.10 em diante.
|
|
153
|
+
|
|
154
|
+
### Limitação conhecida (não corrigida nesta versão)
|
|
155
|
+
|
|
156
|
+
⚠️ **Múltiplos processos escrevendo no mesmo arquivo quebram a cadeia.** Cada
|
|
157
|
+
processo mantém seu próprio `last_hash` em memória e não há `flock` no append,
|
|
158
|
+
então dois workers gunicorn apontando para o mesmo JSONL produzem uma cadeia
|
|
159
|
+
inconsistente — e `verify()` acusa corrupção. O modo de falha é silencioso até
|
|
160
|
+
alguém rodar `verify()`. Ver **P2-4** no `BACKLOG2.md`.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Makefile compartilhado dos `rust_py_*`.
|
|
2
|
+
#
|
|
3
|
+
# Existe para que a CI e a pessoa rodem exatamente o mesmo comando. Quando o
|
|
4
|
+
# lint da CI diverge do lint local, quem perde é sempre quem abriu o PR.
|
|
5
|
+
#
|
|
6
|
+
# A toolchain NÃO é fixada por `rust-toolchain.toml` de propósito: esse arquivo
|
|
7
|
+
# vale para toda invocação do cargo, inclusive a dos containers manylinux do
|
|
8
|
+
# release, e um download de toolchain no meio de uma publicação no PyPI é risco
|
|
9
|
+
# sem contrapartida. A fixação vive no job de lint da CI, que é onde
|
|
10
|
+
# `-D warnings` realmente morde. Localmente, use uma stable recente.
|
|
11
|
+
|
|
12
|
+
PYTHON ?= python3
|
|
13
|
+
|
|
14
|
+
# Fixa qual Python o pyo3 usa na compilação. Sem isto ele pega o primeiro do
|
|
15
|
+
# PATH, que pode não ser o mesmo do `make dev` — e aí o binário de teste linka
|
|
16
|
+
# contra uma libpython e o pytest roda contra outra.
|
|
17
|
+
export PYO3_PYTHON ?= $(PYTHON)
|
|
18
|
+
|
|
19
|
+
# No macOS o binário de `cargo test` carrega a libpython em runtime, e o dyld
|
|
20
|
+
# não a encontra sozinho: o erro é `Library not loaded: @rpath/libpython3.X.dylib`,
|
|
21
|
+
# em SIGABRT, depois de compilar com sucesso — parece falha de teste e não é.
|
|
22
|
+
# Os caminhos padrão entram junto porque definir DYLD_FALLBACK_LIBRARY_PATH
|
|
23
|
+
# *substitui* a lista default do dyld em vez de acrescentar a ela (senão o
|
|
24
|
+
# erro seguinte é libiconv.2.dylib).
|
|
25
|
+
PY_LIBDIR := $(shell $(PYTHON) -c 'import sysconfig; print(sysconfig.get_config_var("LIBDIR") or "")' 2>/dev/null)
|
|
26
|
+
export DYLD_FALLBACK_LIBRARY_PATH := $(PY_LIBDIR):/opt/homebrew/lib:/usr/local/lib:/usr/lib
|
|
27
|
+
|
|
28
|
+
.PHONY: help dev test lint fmt clippy test-rust test-python clean
|
|
29
|
+
|
|
30
|
+
help:
|
|
31
|
+
@echo "make dev instala o pacote em modo editável com os extras de dev"
|
|
32
|
+
@echo "make test cargo test + pytest"
|
|
33
|
+
@echo "make lint cargo fmt --check + cargo clippy -D warnings"
|
|
34
|
+
@echo "make fmt aplica a formatação (escreve nos arquivos)"
|
|
35
|
+
|
|
36
|
+
# `pip install` aciona o backend maturin, compila a extensão e instala.
|
|
37
|
+
# Ao contrário de `maturin develop`, não exige um virtualenv ativo — que é
|
|
38
|
+
# exatamente a armadilha que custou um release do monitor (ver P1-9).
|
|
39
|
+
dev:
|
|
40
|
+
$(PYTHON) -m pip install --upgrade pip
|
|
41
|
+
$(PYTHON) -m pip install -e ".[dev]"
|
|
42
|
+
|
|
43
|
+
test: test-rust test-python
|
|
44
|
+
|
|
45
|
+
# --no-default-features desliga `extension-module`, que impede o binário de
|
|
46
|
+
# teste de linkar contra a libpython no Linux. --lib porque os testes de
|
|
47
|
+
# integração precisam do módulo nativo, e esses ficam do lado Python.
|
|
48
|
+
test-rust:
|
|
49
|
+
cargo test --no-default-features --lib
|
|
50
|
+
|
|
51
|
+
test-python:
|
|
52
|
+
pytest -q
|
|
53
|
+
|
|
54
|
+
lint: fmt-check clippy
|
|
55
|
+
|
|
56
|
+
.PHONY: fmt-check
|
|
57
|
+
fmt-check:
|
|
58
|
+
cargo fmt --all -- --check
|
|
59
|
+
|
|
60
|
+
clippy:
|
|
61
|
+
cargo clippy --all-targets --all-features -- -D warnings
|
|
62
|
+
|
|
63
|
+
fmt:
|
|
64
|
+
cargo fmt --all
|
|
65
|
+
|
|
66
|
+
clean:
|
|
67
|
+
cargo clean
|
|
68
|
+
rm -rf dist build *.egg-info .pytest_cache
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rust-py-audit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
7
6
|
Classifier: Programming Language :: Rust
|
|
8
7
|
Classifier: Programming Language :: Python :: 3
|
|
9
8
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -18,7 +17,7 @@ Requires-Dist: httpx>=0.24 ; extra == 'dev'
|
|
|
18
17
|
Requires-Dist: fastapi>=0.100.0 ; extra == 'dev'
|
|
19
18
|
Requires-Dist: starlette>=0.27.0 ; extra == 'dev'
|
|
20
19
|
Requires-Dist: django>=4.0 ; extra == 'dev'
|
|
21
|
-
Requires-Dist: maturin>=1.
|
|
20
|
+
Requires-Dist: maturin>=1.14 ; extra == 'dev'
|
|
22
21
|
Requires-Dist: django>=4.0 ; extra == 'django'
|
|
23
22
|
Requires-Dist: fastapi>=0.100.0 ; extra == 'fastapi'
|
|
24
23
|
Requires-Dist: starlette>=0.27.0 ; extra == 'fastapi'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Configuração compartilhada do clippy para os cinco `rust_py_*`.
|
|
2
|
+
#
|
|
3
|
+
# O padrão do clippy para `too_many_arguments` é 7. Estes projetos expõem
|
|
4
|
+
# construtores PyO3 que espelham a assinatura Python — `add_job()` do
|
|
5
|
+
# scheduler leva 8 parâmetros porque a API pública leva 8 (retry_backoff,
|
|
6
|
+
# timeout, max_instances e afins). Reagrupar em struct para agradar o lint
|
|
7
|
+
# pioraria a API do lado Python, que é a que o usuário vê.
|
|
8
|
+
#
|
|
9
|
+
# O teto sobe para 10 em vez de o lint ser desligado: acima de 10 o aviso
|
|
10
|
+
# volta a ser informação útil.
|
|
11
|
+
too-many-arguments-threshold = 10
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
|
|
2
|
+
# Piso 1.14 de propósito: versões anteriores não emitem o `License-Expression`
|
|
3
|
+
# do PEP 639, e a 0.3.2 foi ao PyPI sem metadado de licença nenhum (ver P1-4).
|
|
4
|
+
requires = ["maturin>=1.14,<2.0"]
|
|
3
5
|
build-backend = "maturin"
|
|
4
6
|
|
|
5
7
|
[project]
|
|
6
8
|
name = "rust-py-audit"
|
|
7
|
-
version = "0.3.
|
|
9
|
+
version = "0.3.3"
|
|
8
10
|
description = "Fast, tamper-evident event auditing (hash chain) for Python apps, with a Rust core."
|
|
9
11
|
readme = "README.md"
|
|
12
|
+
# Forma do PEP 639: expressão SPDX + arquivos de licença declarados. O
|
|
13
|
+
# classifier "License :: OSI Approved" saiu — é mutuamente exclusivo com
|
|
14
|
+
# License-Expression no Metadata 2.4.
|
|
10
15
|
license = "MIT"
|
|
16
|
+
license-files = ["LICENSE"]
|
|
11
17
|
authors = [
|
|
12
18
|
{ name = "Roberto Lima", email = "robertolima.izphera@gmail.com" }
|
|
13
19
|
]
|
|
@@ -15,7 +21,6 @@ keywords = ["audit", "audit-log", "security", "django", "fastapi", "rust", "inte
|
|
|
15
21
|
classifiers = [
|
|
16
22
|
"Development Status :: 3 - Alpha",
|
|
17
23
|
"Intended Audience :: Developers",
|
|
18
|
-
"License :: OSI Approved :: MIT License",
|
|
19
24
|
"Programming Language :: Rust",
|
|
20
25
|
"Programming Language :: Python :: 3",
|
|
21
26
|
"Programming Language :: Python :: 3.10",
|
|
@@ -40,7 +45,7 @@ dev = [
|
|
|
40
45
|
"fastapi>=0.100.0",
|
|
41
46
|
"starlette>=0.27.0",
|
|
42
47
|
"django>=4.0",
|
|
43
|
-
"maturin>=1.
|
|
48
|
+
"maturin>=1.14",
|
|
44
49
|
]
|
|
45
50
|
|
|
46
51
|
[project.urls]
|