traceseed 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 (59) hide show
  1. traceseed-0.1.0/LICENSE +21 -0
  2. traceseed-0.1.0/PKG-INFO +350 -0
  3. traceseed-0.1.0/README.md +326 -0
  4. traceseed-0.1.0/pyproject.toml +65 -0
  5. traceseed-0.1.0/setup.cfg +4 -0
  6. traceseed-0.1.0/src/traceseed/__init__.py +104 -0
  7. traceseed-0.1.0/src/traceseed/api.py +473 -0
  8. traceseed-0.1.0/src/traceseed/cli.py +171 -0
  9. traceseed-0.1.0/src/traceseed/collectors/__init__.py +199 -0
  10. traceseed-0.1.0/src/traceseed/config.py +162 -0
  11. traceseed-0.1.0/src/traceseed/context.py +68 -0
  12. traceseed-0.1.0/src/traceseed/engine.py +322 -0
  13. traceseed-0.1.0/src/traceseed/errors.py +35 -0
  14. traceseed-0.1.0/src/traceseed/fingerprint.py +131 -0
  15. traceseed-0.1.0/src/traceseed/logging.py +24 -0
  16. traceseed-0.1.0/src/traceseed/models.py +104 -0
  17. traceseed-0.1.0/src/traceseed/py.typed +0 -0
  18. traceseed-0.1.0/src/traceseed/redaction.py +235 -0
  19. traceseed-0.1.0/src/traceseed/replay/__init__.py +3 -0
  20. traceseed-0.1.0/src/traceseed/replay/runner.py +121 -0
  21. traceseed-0.1.0/src/traceseed/serialization.py +263 -0
  22. traceseed-0.1.0/src/traceseed/storage/__init__.py +6 -0
  23. traceseed-0.1.0/src/traceseed/storage/archive.py +400 -0
  24. traceseed-0.1.0/src/traceseed/storage/base.py +22 -0
  25. traceseed-0.1.0/src/traceseed/storage/directory.py +80 -0
  26. traceseed-0.1.0/src/traceseed/storage/memory.py +24 -0
  27. traceseed-0.1.0/src/traceseed.egg-info/PKG-INFO +350 -0
  28. traceseed-0.1.0/src/traceseed.egg-info/SOURCES.txt +57 -0
  29. traceseed-0.1.0/src/traceseed.egg-info/dependency_links.txt +1 -0
  30. traceseed-0.1.0/src/traceseed.egg-info/entry_points.txt +2 -0
  31. traceseed-0.1.0/src/traceseed.egg-info/requires.txt +5 -0
  32. traceseed-0.1.0/src/traceseed.egg-info/top_level.txt +1 -0
  33. traceseed-0.1.0/tests/test_archive_limits.py +226 -0
  34. traceseed-0.1.0/tests/test_asyncio_hook_idempotency.py +146 -0
  35. traceseed-0.1.0/tests/test_atomic_storage.py +90 -0
  36. traceseed-0.1.0/tests/test_branding.py +80 -0
  37. traceseed-0.1.0/tests/test_broken_exceptions.py +128 -0
  38. traceseed-0.1.0/tests/test_capture.py +413 -0
  39. traceseed-0.1.0/tests/test_cli_replay.py +149 -0
  40. traceseed-0.1.0/tests/test_concurrency.py +103 -0
  41. traceseed-0.1.0/tests/test_config.py +81 -0
  42. traceseed-0.1.0/tests/test_config_security_limits.py +151 -0
  43. traceseed-0.1.0/tests/test_context.py +88 -0
  44. traceseed-0.1.0/tests/test_documentation_consistency.py +142 -0
  45. traceseed-0.1.0/tests/test_exception_cycles.py +102 -0
  46. traceseed-0.1.0/tests/test_fingerprint.py +72 -0
  47. traceseed-0.1.0/tests/test_fingerprint_portability.py +235 -0
  48. traceseed-0.1.0/tests/test_fingerprint_versioning.py +158 -0
  49. traceseed-0.1.0/tests/test_hooks_preservation.py +123 -0
  50. traceseed-0.1.0/tests/test_internal_failure_boundary.py +195 -0
  51. traceseed-0.1.0/tests/test_manifest_validation.py +249 -0
  52. traceseed-0.1.0/tests/test_redaction.py +129 -0
  53. traceseed-0.1.0/tests/test_replay_integrity.py +135 -0
  54. traceseed-0.1.0/tests/test_replay_security.py +285 -0
  55. traceseed-0.1.0/tests/test_security_redaction_complete.py +113 -0
  56. traceseed-0.1.0/tests/test_serialization.py +174 -0
  57. traceseed-0.1.0/tests/test_storage.py +146 -0
  58. traceseed-0.1.0/tests/test_strict_semantics.py +126 -0
  59. traceseed-0.1.0/tests/test_zip_metadata_validation.py +200 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Igor Souza
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,350 @@
1
+ Metadata-Version: 2.4
2
+ Name: traceseed
3
+ Version: 0.1.0
4
+ Summary: Dependency-free Python failure capture, fingerprinting and assisted replay
5
+ Author: Igor Souza
6
+ License: MIT
7
+ Keywords: errors,debugging,traceback,observability,replay
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.11
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=8; extra == "dev"
21
+ Requires-Dist: mypy>=1.10; extra == "dev"
22
+ Requires-Dist: ruff>=0.4; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+ # TraceSeed
26
+
27
+ **Turn Python failures into verifiable, reproducible diagnostic packages.**
28
+
29
+ TraceSeed is a modular library with **zero runtime dependencies**. It captures an exception, collects useful context, removes sensitive information, generates a stable fingerprint, and saves a `.tseed` package with integrity hashes.
30
+
31
+ > **Status:** initial release `0.1.0`, ready for study, controlled use, and further development. Replay is assisted and should only be used with trusted packages.
32
+
33
+ ---
34
+
35
+ ## Features
36
+
37
+ - **Small API:** `@capture`, `guard()`, and `capture_exception()`.
38
+ - Synchronous and asynchronous support.
39
+ - Chained exceptions, notes, and `ExceptionGroup`.
40
+ - Arguments, locals, traceback, runtime info, threads, and breadcrumbs.
41
+ - Deep sanitization by field name, regex, and custom function.
42
+ - Stable fingerprinting that normalizes IDs, numbers, UUIDs, long tokens, and hex addresses.
43
+ - `.tseed` packages in ZIP format with a manifest and SHA-256 hashes.
44
+ - Atomic writes to prevent incomplete packages.
45
+ - File, directory, and in-memory storage backends.
46
+ - Extensible collectors and serializers.
47
+ - CLI for viewing, verifying, listing, comparing, and replaying packages.
48
+ - Global hooks for `sys`, `threading`, and `asyncio`.
49
+ - Over 330 regression tests, with lint and static type checking.
50
+
51
+ ---
52
+
53
+ ## Requirements
54
+
55
+ - Python 3.11 or higher.
56
+ - No external runtime dependencies.
57
+
58
+ ---
59
+
60
+ ## Quick start (no install)
61
+
62
+ From the project root:
63
+
64
+ ```bash
65
+ PYTHONPATH=src python examples/basic.py
66
+ PYTHONPATH=src python -m traceseed --version
67
+ ```
68
+
69
+ On Windows PowerShell:
70
+
71
+ ```powershell
72
+ $env:PYTHONPATH = "src"
73
+ python examples/basic.py
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Installation
79
+
80
+ ```bash
81
+ python -m pip install .
82
+ ```
83
+
84
+ No runtime dependencies are declared.
85
+
86
+ ---
87
+
88
+ ## Basic example
89
+
90
+ ```python
91
+ from traceseed import capture
92
+
93
+
94
+ @capture(operation="process-payment")
95
+ def process_payment(order_id: int, token: str) -> None:
96
+ raise ValueError(f"payment rejected for order {order_id}")
97
+
98
+
99
+ process_payment(123, token="secret-token")
100
+ ```
101
+
102
+ The original exception is re-raised. A package is created under `.traceseeds/`:
103
+
104
+ ```text
105
+ .traceseeds/
106
+ └── process-payment-traceseed-9c41...-2ac39f10.tseed
107
+ ```
108
+
109
+ The token is redacted before persistence.
110
+
111
+ ---
112
+
113
+ ## Context manager
114
+
115
+ ```python
116
+ from traceseed import guard
117
+
118
+ with guard("import-customers", metadata={"file": "customers.csv"}):
119
+ import_customers()
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Manual capture
125
+
126
+ ```python
127
+ from traceseed import capture_exception
128
+
129
+ try:
130
+ execute_job()
131
+ except Exception as error:
132
+ result = capture_exception(
133
+ error,
134
+ operation="background-job",
135
+ metadata={"job_id": 42},
136
+ )
137
+ raise
138
+ ```
139
+
140
+ By default, an internal TraceSeed failure never replaces the original exception. Use `strict=True` in tests or admin tools to surface capture errors explicitly.
141
+
142
+ ---
143
+
144
+ ## Configuration
145
+
146
+ ```python
147
+ from pathlib import Path
148
+ from traceseed import TraceSeedConfig, configure
149
+
150
+ configure(
151
+ TraceSeedConfig(
152
+ output_directory=Path("var/traceseeds"),
153
+ capture_arguments=True,
154
+ capture_locals=True,
155
+ capture_argv=False, # disabled by default — argv may contain secrets
156
+ capture_threads=False,
157
+ max_depth=6,
158
+ max_collection_items=80,
159
+ max_operation_length=256,
160
+ max_exception_depth=20,
161
+ max_exception_children=32,
162
+ ).with_redact_fields({"cpf", "session_id"})
163
+ )
164
+ ```
165
+
166
+ ### Security-relevant defaults
167
+
168
+ | Field | Default | Notes |
169
+ |---|---|---|
170
+ | `capture_argv` | `False` | `sys.argv` may contain secrets; opt in explicitly |
171
+ | `capture_cwd` | `True` | working directory is low-risk; disable if needed |
172
+ | `max_exception_depth` | `20` | limits chained-exception recursion |
173
+ | `max_exception_children` | `32` | limits `ExceptionGroup` children |
174
+ | `max_operation_length` | `256` | operation name is truncated to this length |
175
+ | `max_replay_payload_size` | `1 MB` | replay payloads larger than this are rejected |
176
+
177
+ ---
178
+
179
+ ## Context and breadcrumbs
180
+
181
+ ```python
182
+ from traceseed import breadcrumb, context
183
+
184
+ with context(request_id="req-123", tenant="company-a"):
185
+ breadcrumb("database", "customer loaded", customer_id=42)
186
+ breadcrumb("payment", "gateway request sent")
187
+ process_payment()
188
+ ```
189
+
190
+ Context uses `contextvars` and stays isolated across async tasks.
191
+
192
+ ---
193
+
194
+ ## Logs as breadcrumbs
195
+
196
+ ```python
197
+ import logging
198
+ from traceseed import BreadcrumbHandler
199
+
200
+ handler = BreadcrumbHandler()
201
+ logging.getLogger().addHandler(handler)
202
+ ```
203
+
204
+ ---
205
+
206
+ ## Storage backends
207
+
208
+ ```python
209
+ from traceseed import TraceSeedConfig
210
+ from traceseed.serialization import SafeSerializer
211
+ from traceseed.storage import ArchiveStorage, DirectoryStorage, MemoryStorage
212
+
213
+ config = TraceSeedConfig()
214
+ serializer = SafeSerializer(config)
215
+
216
+ archive = ArchiveStorage(config, serializer) # .tseed ZIP files
217
+ directory = DirectoryStorage(config, serializer) # unpacked directory
218
+ memory = MemoryStorage() # in-memory, useful in tests
219
+ ```
220
+
221
+ Pass a storage per capture:
222
+
223
+ ```python
224
+ @capture(storage=memory)
225
+ def operation():
226
+ ...
227
+ ```
228
+
229
+ A custom storage only needs to implement:
230
+
231
+ ```python
232
+ class MyStorage:
233
+ name = "my-storage"
234
+
235
+ def save(self, record, extra=None):
236
+ ...
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Custom collectors
242
+
243
+ ```python
244
+ from traceseed import register_collector
245
+
246
+
247
+ class TenantCollector:
248
+ name = "tenant"
249
+
250
+ def collect(self, exception, context, config):
251
+ return {"tenant_runtime": read_current_tenant()}
252
+
253
+
254
+ register_collector(TenantCollector())
255
+ ```
256
+
257
+ A failing collector is recorded in `collector_errors` and does not block the others.
258
+
259
+ ---
260
+
261
+ ## Assisted replay
262
+
263
+ ```python
264
+ @capture(operation="calculate-tax", replayable=True)
265
+ def calculate_tax(amount, rate):
266
+ return amount * rate
267
+ ```
268
+
269
+ Replay is generated only when the callable is importable and all arguments are reconstructable. If any argument was redacted or could not be serialized, `replay.json` contains `{"replayable": false}` and the runner refuses to execute.
270
+
271
+ ```bash
272
+ traceseed replay failure.tseed --allow-code-execution
273
+ ```
274
+
275
+ > **Warning:** replay imports modules and executes application code. Never replay a package received from an untrusted source.
276
+
277
+ ---
278
+
279
+ ## CLI
280
+
281
+ ```bash
282
+ traceseed show error.tseed
283
+ traceseed show error.tseed --json
284
+ traceseed verify error.tseed
285
+ traceseed list .traceseeds
286
+ traceseed compare first.tseed second.tseed
287
+ traceseed replay error.tseed --allow-code-execution
288
+ ```
289
+
290
+ Without installation:
291
+
292
+ ```bash
293
+ PYTHONPATH=src python -m traceseed show error.tseed
294
+ ```
295
+
296
+ ---
297
+
298
+ ## Development
299
+
300
+ TraceSeed has **zero runtime dependencies**. Development, testing, linting, and type checking use pytest, Ruff, and mypy.
301
+
302
+ ```bash
303
+ python -m ruff format --check .
304
+ python -m ruff check .
305
+ python -m mypy src
306
+ python -m pytest
307
+ ```
308
+
309
+ The test suite covers over 330 scenarios, including package corruption, ZIP bomb protection, exception cycles, broken `repr()`, async concurrency, global hooks, collector failures, and replay.
310
+
311
+ ---
312
+
313
+ ## Project layout
314
+
315
+ ```text
316
+ src/traceseed/
317
+ ├── api.py public API and hooks
318
+ ├── engine.py capture orchestration
319
+ ├── config.py immutable configuration
320
+ ├── context.py context and breadcrumbs
321
+ ├── fingerprint.py stable failure grouping
322
+ ├── redaction.py secret removal
323
+ ├── serialization.py safe JSON codec
324
+ ├── collectors/ independent data collectors
325
+ ├── storage/ file, directory, and memory backends
326
+ ├── replay/ assisted reproduction
327
+ └── cli.py administrative commands
328
+ ```
329
+
330
+ See also:
331
+
332
+ - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
333
+ - [docs/SECURITY.md](docs/SECURITY.md)
334
+ - [docs/EXTENDING.md](docs/EXTENDING.md)
335
+ - [CONTRIBUTING.md](CONTRIBUTING.md)
336
+
337
+ ---
338
+
339
+ ## Known limitations
340
+
341
+ - Replay does not automatically recreate databases, network, external files, or global state.
342
+ - Arbitrary objects are represented for diagnostic purposes but are not automatically reconstructed.
343
+ - Replay isolation is not a security sandbox.
344
+ - Capturing locals may record sensitive data — maintain proper sanitization and limits.
345
+
346
+ ---
347
+
348
+ ## License
349
+
350
+ MIT.
@@ -0,0 +1,326 @@
1
+ # TraceSeed
2
+
3
+ **Turn Python failures into verifiable, reproducible diagnostic packages.**
4
+
5
+ TraceSeed is a modular library with **zero runtime dependencies**. It captures an exception, collects useful context, removes sensitive information, generates a stable fingerprint, and saves a `.tseed` package with integrity hashes.
6
+
7
+ > **Status:** initial release `0.1.0`, ready for study, controlled use, and further development. Replay is assisted and should only be used with trusted packages.
8
+
9
+ ---
10
+
11
+ ## Features
12
+
13
+ - **Small API:** `@capture`, `guard()`, and `capture_exception()`.
14
+ - Synchronous and asynchronous support.
15
+ - Chained exceptions, notes, and `ExceptionGroup`.
16
+ - Arguments, locals, traceback, runtime info, threads, and breadcrumbs.
17
+ - Deep sanitization by field name, regex, and custom function.
18
+ - Stable fingerprinting that normalizes IDs, numbers, UUIDs, long tokens, and hex addresses.
19
+ - `.tseed` packages in ZIP format with a manifest and SHA-256 hashes.
20
+ - Atomic writes to prevent incomplete packages.
21
+ - File, directory, and in-memory storage backends.
22
+ - Extensible collectors and serializers.
23
+ - CLI for viewing, verifying, listing, comparing, and replaying packages.
24
+ - Global hooks for `sys`, `threading`, and `asyncio`.
25
+ - Over 330 regression tests, with lint and static type checking.
26
+
27
+ ---
28
+
29
+ ## Requirements
30
+
31
+ - Python 3.11 or higher.
32
+ - No external runtime dependencies.
33
+
34
+ ---
35
+
36
+ ## Quick start (no install)
37
+
38
+ From the project root:
39
+
40
+ ```bash
41
+ PYTHONPATH=src python examples/basic.py
42
+ PYTHONPATH=src python -m traceseed --version
43
+ ```
44
+
45
+ On Windows PowerShell:
46
+
47
+ ```powershell
48
+ $env:PYTHONPATH = "src"
49
+ python examples/basic.py
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Installation
55
+
56
+ ```bash
57
+ python -m pip install .
58
+ ```
59
+
60
+ No runtime dependencies are declared.
61
+
62
+ ---
63
+
64
+ ## Basic example
65
+
66
+ ```python
67
+ from traceseed import capture
68
+
69
+
70
+ @capture(operation="process-payment")
71
+ def process_payment(order_id: int, token: str) -> None:
72
+ raise ValueError(f"payment rejected for order {order_id}")
73
+
74
+
75
+ process_payment(123, token="secret-token")
76
+ ```
77
+
78
+ The original exception is re-raised. A package is created under `.traceseeds/`:
79
+
80
+ ```text
81
+ .traceseeds/
82
+ └── process-payment-traceseed-9c41...-2ac39f10.tseed
83
+ ```
84
+
85
+ The token is redacted before persistence.
86
+
87
+ ---
88
+
89
+ ## Context manager
90
+
91
+ ```python
92
+ from traceseed import guard
93
+
94
+ with guard("import-customers", metadata={"file": "customers.csv"}):
95
+ import_customers()
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Manual capture
101
+
102
+ ```python
103
+ from traceseed import capture_exception
104
+
105
+ try:
106
+ execute_job()
107
+ except Exception as error:
108
+ result = capture_exception(
109
+ error,
110
+ operation="background-job",
111
+ metadata={"job_id": 42},
112
+ )
113
+ raise
114
+ ```
115
+
116
+ By default, an internal TraceSeed failure never replaces the original exception. Use `strict=True` in tests or admin tools to surface capture errors explicitly.
117
+
118
+ ---
119
+
120
+ ## Configuration
121
+
122
+ ```python
123
+ from pathlib import Path
124
+ from traceseed import TraceSeedConfig, configure
125
+
126
+ configure(
127
+ TraceSeedConfig(
128
+ output_directory=Path("var/traceseeds"),
129
+ capture_arguments=True,
130
+ capture_locals=True,
131
+ capture_argv=False, # disabled by default — argv may contain secrets
132
+ capture_threads=False,
133
+ max_depth=6,
134
+ max_collection_items=80,
135
+ max_operation_length=256,
136
+ max_exception_depth=20,
137
+ max_exception_children=32,
138
+ ).with_redact_fields({"cpf", "session_id"})
139
+ )
140
+ ```
141
+
142
+ ### Security-relevant defaults
143
+
144
+ | Field | Default | Notes |
145
+ |---|---|---|
146
+ | `capture_argv` | `False` | `sys.argv` may contain secrets; opt in explicitly |
147
+ | `capture_cwd` | `True` | working directory is low-risk; disable if needed |
148
+ | `max_exception_depth` | `20` | limits chained-exception recursion |
149
+ | `max_exception_children` | `32` | limits `ExceptionGroup` children |
150
+ | `max_operation_length` | `256` | operation name is truncated to this length |
151
+ | `max_replay_payload_size` | `1 MB` | replay payloads larger than this are rejected |
152
+
153
+ ---
154
+
155
+ ## Context and breadcrumbs
156
+
157
+ ```python
158
+ from traceseed import breadcrumb, context
159
+
160
+ with context(request_id="req-123", tenant="company-a"):
161
+ breadcrumb("database", "customer loaded", customer_id=42)
162
+ breadcrumb("payment", "gateway request sent")
163
+ process_payment()
164
+ ```
165
+
166
+ Context uses `contextvars` and stays isolated across async tasks.
167
+
168
+ ---
169
+
170
+ ## Logs as breadcrumbs
171
+
172
+ ```python
173
+ import logging
174
+ from traceseed import BreadcrumbHandler
175
+
176
+ handler = BreadcrumbHandler()
177
+ logging.getLogger().addHandler(handler)
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Storage backends
183
+
184
+ ```python
185
+ from traceseed import TraceSeedConfig
186
+ from traceseed.serialization import SafeSerializer
187
+ from traceseed.storage import ArchiveStorage, DirectoryStorage, MemoryStorage
188
+
189
+ config = TraceSeedConfig()
190
+ serializer = SafeSerializer(config)
191
+
192
+ archive = ArchiveStorage(config, serializer) # .tseed ZIP files
193
+ directory = DirectoryStorage(config, serializer) # unpacked directory
194
+ memory = MemoryStorage() # in-memory, useful in tests
195
+ ```
196
+
197
+ Pass a storage per capture:
198
+
199
+ ```python
200
+ @capture(storage=memory)
201
+ def operation():
202
+ ...
203
+ ```
204
+
205
+ A custom storage only needs to implement:
206
+
207
+ ```python
208
+ class MyStorage:
209
+ name = "my-storage"
210
+
211
+ def save(self, record, extra=None):
212
+ ...
213
+ ```
214
+
215
+ ---
216
+
217
+ ## Custom collectors
218
+
219
+ ```python
220
+ from traceseed import register_collector
221
+
222
+
223
+ class TenantCollector:
224
+ name = "tenant"
225
+
226
+ def collect(self, exception, context, config):
227
+ return {"tenant_runtime": read_current_tenant()}
228
+
229
+
230
+ register_collector(TenantCollector())
231
+ ```
232
+
233
+ A failing collector is recorded in `collector_errors` and does not block the others.
234
+
235
+ ---
236
+
237
+ ## Assisted replay
238
+
239
+ ```python
240
+ @capture(operation="calculate-tax", replayable=True)
241
+ def calculate_tax(amount, rate):
242
+ return amount * rate
243
+ ```
244
+
245
+ Replay is generated only when the callable is importable and all arguments are reconstructable. If any argument was redacted or could not be serialized, `replay.json` contains `{"replayable": false}` and the runner refuses to execute.
246
+
247
+ ```bash
248
+ traceseed replay failure.tseed --allow-code-execution
249
+ ```
250
+
251
+ > **Warning:** replay imports modules and executes application code. Never replay a package received from an untrusted source.
252
+
253
+ ---
254
+
255
+ ## CLI
256
+
257
+ ```bash
258
+ traceseed show error.tseed
259
+ traceseed show error.tseed --json
260
+ traceseed verify error.tseed
261
+ traceseed list .traceseeds
262
+ traceseed compare first.tseed second.tseed
263
+ traceseed replay error.tseed --allow-code-execution
264
+ ```
265
+
266
+ Without installation:
267
+
268
+ ```bash
269
+ PYTHONPATH=src python -m traceseed show error.tseed
270
+ ```
271
+
272
+ ---
273
+
274
+ ## Development
275
+
276
+ TraceSeed has **zero runtime dependencies**. Development, testing, linting, and type checking use pytest, Ruff, and mypy.
277
+
278
+ ```bash
279
+ python -m ruff format --check .
280
+ python -m ruff check .
281
+ python -m mypy src
282
+ python -m pytest
283
+ ```
284
+
285
+ The test suite covers over 330 scenarios, including package corruption, ZIP bomb protection, exception cycles, broken `repr()`, async concurrency, global hooks, collector failures, and replay.
286
+
287
+ ---
288
+
289
+ ## Project layout
290
+
291
+ ```text
292
+ src/traceseed/
293
+ ├── api.py public API and hooks
294
+ ├── engine.py capture orchestration
295
+ ├── config.py immutable configuration
296
+ ├── context.py context and breadcrumbs
297
+ ├── fingerprint.py stable failure grouping
298
+ ├── redaction.py secret removal
299
+ ├── serialization.py safe JSON codec
300
+ ├── collectors/ independent data collectors
301
+ ├── storage/ file, directory, and memory backends
302
+ ├── replay/ assisted reproduction
303
+ └── cli.py administrative commands
304
+ ```
305
+
306
+ See also:
307
+
308
+ - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
309
+ - [docs/SECURITY.md](docs/SECURITY.md)
310
+ - [docs/EXTENDING.md](docs/EXTENDING.md)
311
+ - [CONTRIBUTING.md](CONTRIBUTING.md)
312
+
313
+ ---
314
+
315
+ ## Known limitations
316
+
317
+ - Replay does not automatically recreate databases, network, external files, or global state.
318
+ - Arbitrary objects are represented for diagnostic purposes but are not automatically reconstructed.
319
+ - Replay isolation is not a security sandbox.
320
+ - Capturing locals may record sensitive data — maintain proper sanitization and limits.
321
+
322
+ ---
323
+
324
+ ## License
325
+
326
+ MIT.