rust-py-audit 0.3.0__tar.gz → 0.3.2__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.2/.github/workflows/release.yml +208 -0
- rust_py_audit-0.3.2/CHANGELOG.md +50 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/Cargo.lock +1 -1
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/Cargo.toml +1 -1
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/PKG-INFO +11 -1
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/README.md +10 -0
- rust_py_audit-0.3.2/ROADMAP.md +68 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/pyproject.toml +1 -1
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/python/rust_py_audit/__init__.py +1 -1
- rust_py_audit-0.3.0/.github/workflows/release.yml +0 -117
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/.gitignore +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/LICENSE +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/examples/__init__.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/examples/basic_usage.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/examples/django_example/__init__.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/examples/django_example/settings.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/examples/django_example/urls.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/examples/django_example/views.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/examples/fastapi_app.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/examples/immutablelog_basic.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/python/rust_py_audit/django.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/python/rust_py_audit/fastapi.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/src/audit_logger.rs +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/src/errors.rs +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/src/event.rs +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/src/hash.rs +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/src/immutablelog_client.rs +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/src/immutablelog_config.rs +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/src/immutablelog_receipt.rs +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/src/lib.rs +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/src/pyconv.rs +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/src/retry.rs +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/src/storage.rs +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/src/verifier.rs +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/tests/conftest.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/tests/test_audit_logger.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/tests/test_concurrency.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/tests/test_django_immutablelog.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/tests/test_django_middleware.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/tests/test_fastapi_immutablelog.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/tests/test_fastapi_middleware.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/tests/test_flush_pending.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/tests/test_hello.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/tests/test_immutablelog_hybrid.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/tests/test_immutablelog_remote.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/tests/test_metadata_bigint.py +0 -0
- {rust_py_audit-0.3.0 → rust_py_audit-0.3.2}/tests/test_verify.py +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch: {}
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
# Nothing gets built, and nothing reaches PyPI, until this passes. Publishing
|
|
14
|
+
# to PyPI is irreversible: a bad wheel cannot be replaced, only yanked.
|
|
15
|
+
test:
|
|
16
|
+
name: Test (Rust + Python)
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.12"
|
|
24
|
+
|
|
25
|
+
# --no-default-features drops extension-module, which tells the linker to
|
|
26
|
+
# leave libpython symbols undefined. That is correct for the wheel but
|
|
27
|
+
# produces a test binary that cannot link on Linux.
|
|
28
|
+
- name: Rust unit tests
|
|
29
|
+
env:
|
|
30
|
+
PYO3_PYTHON: python
|
|
31
|
+
run: cargo test --no-default-features --lib
|
|
32
|
+
|
|
33
|
+
- name: Build, install the extension and run the Python tests
|
|
34
|
+
# `pip install` drives the maturin build backend, so it compiles the
|
|
35
|
+
# Rust extension and installs it (plus the dev extras) into the
|
|
36
|
+
# runner's Python — no virtualenv needed, unlike `maturin develop`,
|
|
37
|
+
# which aborts when it cannot find one.
|
|
38
|
+
run: |
|
|
39
|
+
python -m pip install --upgrade pip
|
|
40
|
+
python -m pip install ".[dev]"
|
|
41
|
+
python -m pytest
|
|
42
|
+
|
|
43
|
+
linux:
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
needs: test
|
|
46
|
+
strategy:
|
|
47
|
+
matrix:
|
|
48
|
+
target: [x86_64, aarch64]
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
- uses: PyO3/maturin-action@v1
|
|
52
|
+
env:
|
|
53
|
+
# `ring` (puxado por `rustls`, via `reqwest`) falha ao cross-compilar
|
|
54
|
+
# sua assembly pregenerada para aarch64 dentro do container manylinux
|
|
55
|
+
# do maturin-action: o cross-gcc não define `__ARM_ARCH` por padrão
|
|
56
|
+
# nesse ambiente, e o preprocessor de `ring` exige essa macro
|
|
57
|
+
# (erro: "ARM assembler must define __ARM_ARCH"). Forçar a macro via
|
|
58
|
+
# CFLAGS é o workaround padrão para esse problema conhecido — não
|
|
59
|
+
# afeta o build x86_64 (a env var é específica do target aarch64).
|
|
60
|
+
CFLAGS_aarch64_unknown_linux_gnu: -D__ARM_ARCH=8
|
|
61
|
+
with:
|
|
62
|
+
target: ${{ matrix.target }}
|
|
63
|
+
args: --release --out dist --find-interpreter
|
|
64
|
+
manylinux: auto
|
|
65
|
+
- uses: actions/upload-artifact@v4
|
|
66
|
+
with:
|
|
67
|
+
name: wheels-linux-${{ matrix.target }}
|
|
68
|
+
path: dist
|
|
69
|
+
|
|
70
|
+
musllinux:
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
needs: test
|
|
73
|
+
strategy:
|
|
74
|
+
matrix:
|
|
75
|
+
target: [x86_64, aarch64]
|
|
76
|
+
steps:
|
|
77
|
+
- uses: actions/checkout@v4
|
|
78
|
+
- uses: PyO3/maturin-action@v1
|
|
79
|
+
with:
|
|
80
|
+
target: ${{ matrix.target }}
|
|
81
|
+
args: --release --out dist --find-interpreter
|
|
82
|
+
manylinux: musllinux_1_2
|
|
83
|
+
- uses: actions/upload-artifact@v4
|
|
84
|
+
with:
|
|
85
|
+
name: wheels-musllinux-${{ matrix.target }}
|
|
86
|
+
path: dist
|
|
87
|
+
|
|
88
|
+
windows:
|
|
89
|
+
runs-on: windows-latest
|
|
90
|
+
needs: test
|
|
91
|
+
steps:
|
|
92
|
+
- uses: actions/checkout@v4
|
|
93
|
+
- uses: actions/setup-python@v5
|
|
94
|
+
with:
|
|
95
|
+
architecture: x64
|
|
96
|
+
- uses: PyO3/maturin-action@v1
|
|
97
|
+
with:
|
|
98
|
+
target: x64
|
|
99
|
+
args: --release --out dist --find-interpreter
|
|
100
|
+
- uses: actions/upload-artifact@v4
|
|
101
|
+
with:
|
|
102
|
+
name: wheels-windows-x64
|
|
103
|
+
path: dist
|
|
104
|
+
|
|
105
|
+
macos:
|
|
106
|
+
runs-on: macos-latest
|
|
107
|
+
needs: test
|
|
108
|
+
strategy:
|
|
109
|
+
matrix:
|
|
110
|
+
target: [x86_64, aarch64]
|
|
111
|
+
steps:
|
|
112
|
+
- uses: actions/checkout@v4
|
|
113
|
+
- uses: PyO3/maturin-action@v1
|
|
114
|
+
with:
|
|
115
|
+
target: ${{ matrix.target }}
|
|
116
|
+
args: --release --out dist --find-interpreter
|
|
117
|
+
- uses: actions/upload-artifact@v4
|
|
118
|
+
with:
|
|
119
|
+
name: wheels-macos-${{ matrix.target }}
|
|
120
|
+
path: dist
|
|
121
|
+
|
|
122
|
+
sdist:
|
|
123
|
+
runs-on: ubuntu-latest
|
|
124
|
+
needs: test
|
|
125
|
+
steps:
|
|
126
|
+
- uses: actions/checkout@v4
|
|
127
|
+
- uses: PyO3/maturin-action@v1
|
|
128
|
+
with:
|
|
129
|
+
command: sdist
|
|
130
|
+
args: --out dist
|
|
131
|
+
- uses: actions/upload-artifact@v4
|
|
132
|
+
with:
|
|
133
|
+
name: wheels-sdist
|
|
134
|
+
path: dist
|
|
135
|
+
|
|
136
|
+
release:
|
|
137
|
+
name: Publish to PyPI
|
|
138
|
+
runs-on: ubuntu-latest
|
|
139
|
+
needs: [test, linux, musllinux, windows, macos, sdist]
|
|
140
|
+
steps:
|
|
141
|
+
- uses: actions/download-artifact@v4
|
|
142
|
+
with:
|
|
143
|
+
pattern: wheels-*
|
|
144
|
+
path: dist
|
|
145
|
+
merge-multiple: true
|
|
146
|
+
- uses: actions/setup-python@v5
|
|
147
|
+
with:
|
|
148
|
+
python-version: "3.12"
|
|
149
|
+
- name: Install maturin
|
|
150
|
+
run: python -m pip install --upgrade maturin requests
|
|
151
|
+
|
|
152
|
+
# A sanity check before anything leaves the runner: publishing an
|
|
153
|
+
# incomplete set is worse than not publishing, because PyPI never lets a
|
|
154
|
+
# filename be reused — a half-uploaded version can only be completed,
|
|
155
|
+
# never replaced.
|
|
156
|
+
- name: Verify the artifact set is complete
|
|
157
|
+
run: |
|
|
158
|
+
set -euo pipefail
|
|
159
|
+
ls -la dist/
|
|
160
|
+
test -n "$(find dist -name '*.tar.gz' -print -quit)" \
|
|
161
|
+
|| { echo "::error::sdist ausente — abortando antes de publicar"; exit 1; }
|
|
162
|
+
wheels=$(find dist -name '*.whl' | wc -l)
|
|
163
|
+
echo "wheels: $wheels"
|
|
164
|
+
test "$wheels" -ge 7 \
|
|
165
|
+
|| { echo "::error::só $wheels wheels; esperado >= 7 (2 linux, 2 musl, 1 win, 2 macos)"; exit 1; }
|
|
166
|
+
|
|
167
|
+
# PyPI returns a transient 500 often enough to matter, and the upload is
|
|
168
|
+
# file-by-file: a failure halfway leaves the version published in part.
|
|
169
|
+
# --skip-existing makes the retry idempotent (already-uploaded files are
|
|
170
|
+
# skipped by name), so a second attempt finishes the job instead of
|
|
171
|
+
# failing on the files that did make it.
|
|
172
|
+
- name: Publish to PyPI (with retry)
|
|
173
|
+
uses: nick-fields/retry@v3
|
|
174
|
+
env:
|
|
175
|
+
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
176
|
+
with:
|
|
177
|
+
timeout_minutes: 15
|
|
178
|
+
max_attempts: 4
|
|
179
|
+
retry_wait_seconds: 45
|
|
180
|
+
command: maturin upload --non-interactive --skip-existing dist/*
|
|
181
|
+
|
|
182
|
+
# Proves the release is actually complete, rather than trusting an exit
|
|
183
|
+
# code that a partial upload can still produce.
|
|
184
|
+
- name: Verify every file reached PyPI
|
|
185
|
+
run: |
|
|
186
|
+
set -euo pipefail
|
|
187
|
+
python - <<'PY'
|
|
188
|
+
import os, sys, glob, time, requests
|
|
189
|
+
version = os.environ["GITHUB_REF_NAME"].lstrip("v")
|
|
190
|
+
local = {os.path.basename(p) for p in glob.glob("dist/*")}
|
|
191
|
+
url = f"https://pypi.org/pypi/rust-py-audit/{version}/json"
|
|
192
|
+
for attempt in range(6): # PyPI's JSON API is cached
|
|
193
|
+
response = requests.get(url, timeout=30)
|
|
194
|
+
if response.status_code == 200:
|
|
195
|
+
remote = {f["filename"] for f in response.json()["urls"]}
|
|
196
|
+
missing = local - remote
|
|
197
|
+
if not missing:
|
|
198
|
+
print(f"OK: {len(remote)} arquivos publicados na {version}")
|
|
199
|
+
sys.exit(0)
|
|
200
|
+
else:
|
|
201
|
+
missing = local
|
|
202
|
+
print(f"tentativa {attempt + 1}: faltam {len(missing)}")
|
|
203
|
+
time.sleep(20)
|
|
204
|
+
print("::error::faltaram arquivos no PyPI:")
|
|
205
|
+
for name in sorted(missing):
|
|
206
|
+
print(f" - {name}")
|
|
207
|
+
sys.exit(1)
|
|
208
|
+
PY
|
|
@@ -0,0 +1,50 @@
|
|
|
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.2] - 2026-08-11
|
|
9
|
+
|
|
10
|
+
Release de infraestrutura: nada muda no comportamento da biblioteca.
|
|
11
|
+
|
|
12
|
+
### Corrigido
|
|
13
|
+
|
|
14
|
+
- **A publicação não roda mais sem teste.** O `release.yml` era o único
|
|
15
|
+
workflow do repositório e só tinha jobs de build: `git push --tags`
|
|
16
|
+
publicava no PyPI sem nenhum `cargo test` nem `pytest`. Agora existe um job
|
|
17
|
+
`test` e todos os jobs de wheel, o de sdist e o de release declaram
|
|
18
|
+
`needs: test`. (BACKLOG2 P1-1)
|
|
19
|
+
- **O upload aguenta o 500 do PyPI.** O upload é arquivo por arquivo e sem
|
|
20
|
+
transação — uma falha no meio deixa a versão publicada pela metade, e o PyPI
|
|
21
|
+
nunca permite reusar um nome de arquivo. O passo de publicação agora tem
|
|
22
|
+
retry (4 tentativas), continua usando `--skip-existing` para ser idempotente,
|
|
23
|
+
confere o conjunto de artefatos **antes** de subir qualquer coisa (aborta se
|
|
24
|
+
faltar o sdist) e valida contra a API do PyPI **depois**, listando nome por
|
|
25
|
+
nome o que não chegou. (BACKLOG2 P1-9)
|
|
26
|
+
|
|
27
|
+
### Adicionado
|
|
28
|
+
|
|
29
|
+
- Este `CHANGELOG.md`. (BACKLOG2 P1-5)
|
|
30
|
+
|
|
31
|
+
## [0.3.1] e anteriores
|
|
32
|
+
|
|
33
|
+
Sem changelog retroativo. O histórico do que entrou em cada versão está no
|
|
34
|
+
`ROADMAP.md`, e o das mudanças no `git log`.
|
|
35
|
+
|
|
36
|
+
Resumo do que o projeto entrega hoje:
|
|
37
|
+
|
|
38
|
+
- `AuditLogger` com log append-only em JSONL e cadeia de hashes encadeada.
|
|
39
|
+
- `verify()` para detectar adulteração da cadeia.
|
|
40
|
+
- Integração com o ImmutableLog nos modos remoto e híbrido.
|
|
41
|
+
- Middlewares para FastAPI e Django.
|
|
42
|
+
- Wheels `abi3-py310` — uma wheel por plataforma, válida da 3.10 em diante.
|
|
43
|
+
|
|
44
|
+
### Limitação conhecida (não corrigida nesta versão)
|
|
45
|
+
|
|
46
|
+
⚠️ **Múltiplos processos escrevendo no mesmo arquivo quebram a cadeia.** Cada
|
|
47
|
+
processo mantém seu próprio `last_hash` em memória e não há `flock` no append,
|
|
48
|
+
então dois workers gunicorn apontando para o mesmo JSONL produzem uma cadeia
|
|
49
|
+
inconsistente — e `verify()` acusa corrupção. O modo de falha é silencioso até
|
|
50
|
+
alguém rodar `verify()`. Ver **P2-4** no `BACKLOG2.md`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rust-py-audit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -409,6 +409,16 @@ For each queued event:
|
|
|
409
409
|
|
|
410
410
|
---
|
|
411
411
|
|
|
412
|
+
## Roadmap
|
|
413
|
+
|
|
414
|
+
`rust-py-audit` is mature (v0.3.0): the SHA-256 hash chain, JSONL storage,
|
|
415
|
+
FastAPI/Django middlewares, and the ImmutableLog `local`/`remote`/`hybrid`
|
|
416
|
+
integration are shipped and stable. Directional ideas under consideration (log
|
|
417
|
+
rotation, incremental verification, more delivery backends, an async client, a
|
|
418
|
+
Flask middleware) are tracked in [ROADMAP.md](./ROADMAP.md).
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
412
422
|
## Building from Source
|
|
413
423
|
|
|
414
424
|
Requires Rust and [maturin](https://github.com/PyO3/maturin).
|
|
@@ -372,6 +372,16 @@ For each queued event:
|
|
|
372
372
|
|
|
373
373
|
---
|
|
374
374
|
|
|
375
|
+
## Roadmap
|
|
376
|
+
|
|
377
|
+
`rust-py-audit` is mature (v0.3.0): the SHA-256 hash chain, JSONL storage,
|
|
378
|
+
FastAPI/Django middlewares, and the ImmutableLog `local`/`remote`/`hybrid`
|
|
379
|
+
integration are shipped and stable. Directional ideas under consideration (log
|
|
380
|
+
rotation, incremental verification, more delivery backends, an async client, a
|
|
381
|
+
Flask middleware) are tracked in [ROADMAP.md](./ROADMAP.md).
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
375
385
|
## Building from Source
|
|
376
386
|
|
|
377
387
|
Requires Rust and [maturin](https://github.com/PyO3/maturin).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Roadmap — rust-py-audit
|
|
2
|
+
|
|
3
|
+
Direction for `rust-py-audit`: a tamper-evident audit log for Python with a Rust
|
|
4
|
+
core (PyO3 + maturin). The library is already mature (v0.3.0) — the SHA-256 hash
|
|
5
|
+
chain, JSONL storage, FastAPI/Django middlewares, and the ImmutableLog
|
|
6
|
+
`local`/`remote`/`hybrid` integration are shipped and stable. This document tracks
|
|
7
|
+
what is done and the **directional** ideas under consideration.
|
|
8
|
+
|
|
9
|
+
> Status legend: ✅ shipped · 🔜 planned (next) · 💡 idea (no version yet) · ⚠️ note
|
|
10
|
+
|
|
11
|
+
> ⚠️ Beyond "shipped", items below are **inferred directions, not commitments**.
|
|
12
|
+
> Confirm priorities with the maintainer before planning a release around them.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Shipped — up to v0.3.0
|
|
17
|
+
|
|
18
|
+
- ✅ `AuditLogger` with `log(...)`, `verify()`, `last_hash()`.
|
|
19
|
+
- ✅ **SHA-256 hash chain** — each event embeds the previous event's hash; any
|
|
20
|
+
edit, deletion, or reordering is detected by `verify()` (re-read from disk,
|
|
21
|
+
never trusts an in-memory cache).
|
|
22
|
+
- ✅ Append-only **JSONL** storage — one event per line, no database.
|
|
23
|
+
- ✅ Thread-safe — the chain stays linear under concurrency.
|
|
24
|
+
- ✅ Free-form JSON-serializable `metadata`.
|
|
25
|
+
- ✅ FastAPI and Django middlewares (log state-changing requests:
|
|
26
|
+
POST/PUT/PATCH/DELETE).
|
|
27
|
+
- ✅ **ImmutableLog integration** — `local`/`remote`/`hybrid` modes, automatic
|
|
28
|
+
retry with idempotency key, pending queue (`audit.pending.jsonl`),
|
|
29
|
+
`flush_pending()`, and transient (`pending`) vs permanent (`failed`) failure
|
|
30
|
+
handling that never takes down the application response.
|
|
31
|
+
- ✅ `severity`, `immutable_trail`, and `env` delivery metadata (never enter the
|
|
32
|
+
hash, so `verify()` stays valid after `flush_pending()`).
|
|
33
|
+
- ✅ ImmutableLog HTTP client runs in the Rust core (PyO3 + reqwest).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Directional ideas (no version assigned — confirm before planning)
|
|
38
|
+
|
|
39
|
+
- 💡 **Log rotation / segmentation** — roll the JSONL by size or date while
|
|
40
|
+
preserving chain continuity across segments (carry the last hash forward).
|
|
41
|
+
- 💡 **Incremental verification** — verify only events appended since the last
|
|
42
|
+
known-good hash, instead of re-reading the whole file every time.
|
|
43
|
+
- 💡 **More delivery backends** — additional sinks alongside ImmutableLog (e.g.
|
|
44
|
+
S3/object storage, syslog, a generic webhook), reusing the retry/pending logic.
|
|
45
|
+
- 💡 **Async client** — non-blocking delivery path for high-throughput async apps.
|
|
46
|
+
- 💡 **Flask middleware** — parity with the FastAPI/Django adapters.
|
|
47
|
+
- 💡 **Query/read helpers** — iterate or filter recorded events (by actor, action,
|
|
48
|
+
resource, time range) without hand-parsing the JSONL.
|
|
49
|
+
- 💡 **Benchmarks suite** — hashing + append throughput under concurrency.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Known limitations (by design, for now)
|
|
54
|
+
|
|
55
|
+
- Local JSONL is single-file and append-only (no built-in rotation yet).
|
|
56
|
+
- `verify()` re-reads the entire file (O(n)); fine for typical logs, see the
|
|
57
|
+
incremental-verification idea above for very large files.
|
|
58
|
+
- ImmutableLog delivery is synchronous on the `log()` path (bounded by
|
|
59
|
+
`timeout_ms` + retries); `hybrid` mode never raises, queuing transient failures.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Contributing to the roadmap
|
|
64
|
+
|
|
65
|
+
Versions and ordering are indicative and may shift. Bump the version in **both**
|
|
66
|
+
`Cargo.toml` and `pyproject.toml` (kept in sync) plus `__version__`, ship tests
|
|
67
|
+
(`cargo test --no-default-features` + `pytest`), then tag `vX.Y.Z` to trigger the
|
|
68
|
+
release workflow (Trusted Publishing / OIDC to PyPI).
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
name: Release
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- "v*"
|
|
7
|
-
workflow_dispatch: {}
|
|
8
|
-
|
|
9
|
-
permissions:
|
|
10
|
-
contents: read
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
linux:
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
strategy:
|
|
16
|
-
matrix:
|
|
17
|
-
target: [x86_64, aarch64]
|
|
18
|
-
steps:
|
|
19
|
-
- uses: actions/checkout@v4
|
|
20
|
-
- uses: PyO3/maturin-action@v1
|
|
21
|
-
env:
|
|
22
|
-
# `ring` (puxado por `rustls`, via `reqwest`) falha ao cross-compilar
|
|
23
|
-
# sua assembly pregenerada para aarch64 dentro do container manylinux
|
|
24
|
-
# do maturin-action: o cross-gcc não define `__ARM_ARCH` por padrão
|
|
25
|
-
# nesse ambiente, e o preprocessor de `ring` exige essa macro
|
|
26
|
-
# (erro: "ARM assembler must define __ARM_ARCH"). Forçar a macro via
|
|
27
|
-
# CFLAGS é o workaround padrão para esse problema conhecido — não
|
|
28
|
-
# afeta o build x86_64 (a env var é específica do target aarch64).
|
|
29
|
-
CFLAGS_aarch64_unknown_linux_gnu: -D__ARM_ARCH=8
|
|
30
|
-
with:
|
|
31
|
-
target: ${{ matrix.target }}
|
|
32
|
-
args: --release --out dist --find-interpreter
|
|
33
|
-
manylinux: auto
|
|
34
|
-
- uses: actions/upload-artifact@v4
|
|
35
|
-
with:
|
|
36
|
-
name: wheels-linux-${{ matrix.target }}
|
|
37
|
-
path: dist
|
|
38
|
-
|
|
39
|
-
musllinux:
|
|
40
|
-
runs-on: ubuntu-latest
|
|
41
|
-
strategy:
|
|
42
|
-
matrix:
|
|
43
|
-
target: [x86_64, aarch64]
|
|
44
|
-
steps:
|
|
45
|
-
- uses: actions/checkout@v4
|
|
46
|
-
- uses: PyO3/maturin-action@v1
|
|
47
|
-
with:
|
|
48
|
-
target: ${{ matrix.target }}
|
|
49
|
-
args: --release --out dist --find-interpreter
|
|
50
|
-
manylinux: musllinux_1_2
|
|
51
|
-
- uses: actions/upload-artifact@v4
|
|
52
|
-
with:
|
|
53
|
-
name: wheels-musllinux-${{ matrix.target }}
|
|
54
|
-
path: dist
|
|
55
|
-
|
|
56
|
-
windows:
|
|
57
|
-
runs-on: windows-latest
|
|
58
|
-
steps:
|
|
59
|
-
- uses: actions/checkout@v4
|
|
60
|
-
- uses: actions/setup-python@v5
|
|
61
|
-
with:
|
|
62
|
-
architecture: x64
|
|
63
|
-
- uses: PyO3/maturin-action@v1
|
|
64
|
-
with:
|
|
65
|
-
target: x64
|
|
66
|
-
args: --release --out dist --find-interpreter
|
|
67
|
-
- uses: actions/upload-artifact@v4
|
|
68
|
-
with:
|
|
69
|
-
name: wheels-windows-x64
|
|
70
|
-
path: dist
|
|
71
|
-
|
|
72
|
-
macos:
|
|
73
|
-
runs-on: macos-latest
|
|
74
|
-
strategy:
|
|
75
|
-
matrix:
|
|
76
|
-
target: [x86_64, aarch64]
|
|
77
|
-
steps:
|
|
78
|
-
- uses: actions/checkout@v4
|
|
79
|
-
- uses: PyO3/maturin-action@v1
|
|
80
|
-
with:
|
|
81
|
-
target: ${{ matrix.target }}
|
|
82
|
-
args: --release --out dist --find-interpreter
|
|
83
|
-
- uses: actions/upload-artifact@v4
|
|
84
|
-
with:
|
|
85
|
-
name: wheels-macos-${{ matrix.target }}
|
|
86
|
-
path: dist
|
|
87
|
-
|
|
88
|
-
sdist:
|
|
89
|
-
runs-on: ubuntu-latest
|
|
90
|
-
steps:
|
|
91
|
-
- uses: actions/checkout@v4
|
|
92
|
-
- uses: PyO3/maturin-action@v1
|
|
93
|
-
with:
|
|
94
|
-
command: sdist
|
|
95
|
-
args: --out dist
|
|
96
|
-
- uses: actions/upload-artifact@v4
|
|
97
|
-
with:
|
|
98
|
-
name: wheels-sdist
|
|
99
|
-
path: dist
|
|
100
|
-
|
|
101
|
-
release:
|
|
102
|
-
name: Publish to PyPI
|
|
103
|
-
runs-on: ubuntu-latest
|
|
104
|
-
needs: [linux, musllinux, windows, macos, sdist]
|
|
105
|
-
steps:
|
|
106
|
-
- uses: actions/download-artifact@v4
|
|
107
|
-
with:
|
|
108
|
-
pattern: wheels-*
|
|
109
|
-
path: dist
|
|
110
|
-
merge-multiple: true
|
|
111
|
-
- name: Publish to PyPI
|
|
112
|
-
uses: PyO3/maturin-action@v1
|
|
113
|
-
env:
|
|
114
|
-
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
115
|
-
with:
|
|
116
|
-
command: upload
|
|
117
|
-
args: --non-interactive --skip-existing dist/*
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|