pycustodian 0.0.1__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 (128) hide show
  1. pycustodian-0.0.1/.claude/CLAUDE.md +414 -0
  2. pycustodian-0.0.1/.claude/commands/branch.md +98 -0
  3. pycustodian-0.0.1/.claude/skills/config-change/SKILL.md +122 -0
  4. pycustodian-0.0.1/.claude/skills/onboard/SKILL.md +140 -0
  5. pycustodian-0.0.1/.claude/skills/plan/SKILL.md +113 -0
  6. pycustodian-0.0.1/.claude/skills/refactor/SKILL.md +239 -0
  7. pycustodian-0.0.1/.claude/skills/release/SKILL.md +192 -0
  8. pycustodian-0.0.1/.claude/skills/ship/SKILL.md +218 -0
  9. pycustodian-0.0.1/.claude/skills/verify/SKILL.md +142 -0
  10. pycustodian-0.0.1/.cursor/rules/optophi-project-standards.mdc +40 -0
  11. pycustodian-0.0.1/.github/workflows/ci.yml +100 -0
  12. pycustodian-0.0.1/.github/workflows/publish.yml +120 -0
  13. pycustodian-0.0.1/.github/workflows/scorecards.yml +71 -0
  14. pycustodian-0.0.1/.gitignore +314 -0
  15. pycustodian-0.0.1/.import_linter_cache/.gitignore +2 -0
  16. pycustodian-0.0.1/.import_linter_cache/CACHEDIR.TAG +3 -0
  17. pycustodian-0.0.1/.import_linter_cache/df4d22b0047555b49d58aed898ce423ae203ab5b.data.json +1 -0
  18. pycustodian-0.0.1/.import_linter_cache/pycustodian.meta.json +1 -0
  19. pycustodian-0.0.1/.pre-commit-config.yaml +97 -0
  20. pycustodian-0.0.1/.semgrepignore +56 -0
  21. pycustodian-0.0.1/AGENTS.md +66 -0
  22. pycustodian-0.0.1/ARCHITECTURE.md +87 -0
  23. pycustodian-0.0.1/CHANGELOG.md +35 -0
  24. pycustodian-0.0.1/CLAUDE.md +1 -0
  25. pycustodian-0.0.1/CODE_OF_CONDUCT.md +82 -0
  26. pycustodian-0.0.1/CONTRIBUTING.md +138 -0
  27. pycustodian-0.0.1/LICENSE +21 -0
  28. pycustodian-0.0.1/Makefile +106 -0
  29. pycustodian-0.0.1/PKG-INFO +108 -0
  30. pycustodian-0.0.1/README.md +48 -0
  31. pycustodian-0.0.1/SECURITY.md +21 -0
  32. pycustodian-0.0.1/alembic.ini +41 -0
  33. pycustodian-0.0.1/cliff.toml +49 -0
  34. pycustodian-0.0.1/examples/quickstart.py +81 -0
  35. pycustodian-0.0.1/pyproject.toml +188 -0
  36. pycustodian-0.0.1/scripts/build.sh +118 -0
  37. pycustodian-0.0.1/scripts/ci.sh +252 -0
  38. pycustodian-0.0.1/scripts/pre-push.sh +105 -0
  39. pycustodian-0.0.1/scripts/release.sh +875 -0
  40. pycustodian-0.0.1/scripts/setup.sh +139 -0
  41. pycustodian-0.0.1/setup.sh +129 -0
  42. pycustodian-0.0.1/src/pycustodian/__init__.py +82 -0
  43. pycustodian-0.0.1/src/pycustodian/adapters/__init__.py +11 -0
  44. pycustodian-0.0.1/src/pycustodian/adapters/_codec.py +102 -0
  45. pycustodian-0.0.1/src/pycustodian/adapters/api/__init__.py +7 -0
  46. pycustodian-0.0.1/src/pycustodian/adapters/api/app.py +136 -0
  47. pycustodian-0.0.1/src/pycustodian/adapters/api/schemas.py +95 -0
  48. pycustodian-0.0.1/src/pycustodian/adapters/clock.py +13 -0
  49. pycustodian-0.0.1/src/pycustodian/adapters/errors.py +11 -0
  50. pycustodian-0.0.1/src/pycustodian/adapters/ingest/__init__.py +7 -0
  51. pycustodian-0.0.1/src/pycustodian/adapters/ingest/decode.py +48 -0
  52. pycustodian-0.0.1/src/pycustodian/adapters/ingest/kafka_consumer.py +92 -0
  53. pycustodian-0.0.1/src/pycustodian/adapters/integrations/__init__.py +39 -0
  54. pycustodian-0.0.1/src/pycustodian/adapters/integrations/bridges.py +52 -0
  55. pycustodian-0.0.1/src/pycustodian/adapters/integrations/pyactuator.py +70 -0
  56. pycustodian-0.0.1/src/pycustodian/adapters/integrations/pyfortis.py +74 -0
  57. pycustodian-0.0.1/src/pycustodian/adapters/integrations/pyoptima.py +47 -0
  58. pycustodian-0.0.1/src/pycustodian/adapters/memory/__init__.py +8 -0
  59. pycustodian-0.0.1/src/pycustodian/adapters/memory/event_store.py +51 -0
  60. pycustodian-0.0.1/src/pycustodian/adapters/memory/idempotency.py +23 -0
  61. pycustodian-0.0.1/src/pycustodian/adapters/prices.py +17 -0
  62. pycustodian-0.0.1/src/pycustodian/adapters/runtime.py +53 -0
  63. pycustodian-0.0.1/src/pycustodian/adapters/sql/__init__.py +17 -0
  64. pycustodian-0.0.1/src/pycustodian/adapters/sql/event_store.py +70 -0
  65. pycustodian-0.0.1/src/pycustodian/adapters/sql/migrations/env.py +52 -0
  66. pycustodian-0.0.1/src/pycustodian/adapters/sql/migrations/script.py.mako +27 -0
  67. pycustodian-0.0.1/src/pycustodian/adapters/sql/migrations/versions/0001_initial_event_store.py +39 -0
  68. pycustodian-0.0.1/src/pycustodian/adapters/sql/models.py +43 -0
  69. pycustodian-0.0.1/src/pycustodian/adapters/sql/session.py +25 -0
  70. pycustodian-0.0.1/src/pycustodian/adapters/sql/unit_of_work.py +34 -0
  71. pycustodian-0.0.1/src/pycustodian/application/__init__.py +11 -0
  72. pycustodian-0.0.1/src/pycustodian/application/post_cash.py +83 -0
  73. pycustodian-0.0.1/src/pycustodian/application/queries.py +53 -0
  74. pycustodian-0.0.1/src/pycustodian/application/record_fill.py +129 -0
  75. pycustodian-0.0.1/src/pycustodian/application/replay.py +17 -0
  76. pycustodian-0.0.1/src/pycustodian/application/service.py +123 -0
  77. pycustodian-0.0.1/src/pycustodian/application/value_book.py +20 -0
  78. pycustodian-0.0.1/src/pycustodian/cli.py +246 -0
  79. pycustodian-0.0.1/src/pycustodian/config/__init__.py +10 -0
  80. pycustodian-0.0.1/src/pycustodian/config/loader.py +30 -0
  81. pycustodian-0.0.1/src/pycustodian/config/models.py +28 -0
  82. pycustodian-0.0.1/src/pycustodian/domain/__init__.py +10 -0
  83. pycustodian-0.0.1/src/pycustodian/domain/accounts.py +97 -0
  84. pycustodian-0.0.1/src/pycustodian/domain/errors.py +45 -0
  85. pycustodian-0.0.1/src/pycustodian/domain/instruments.py +34 -0
  86. pycustodian-0.0.1/src/pycustodian/domain/journal.py +120 -0
  87. pycustodian-0.0.1/src/pycustodian/domain/lots.py +181 -0
  88. pycustodian-0.0.1/src/pycustodian/domain/money.py +64 -0
  89. pycustodian-0.0.1/src/pycustodian/domain/performance.py +83 -0
  90. pycustodian-0.0.1/src/pycustodian/domain/pnl.py +19 -0
  91. pycustodian-0.0.1/src/pycustodian/domain/positions.py +48 -0
  92. pycustodian-0.0.1/src/pycustodian/domain/posting.py +160 -0
  93. pycustodian-0.0.1/src/pycustodian/domain/projections.py +96 -0
  94. pycustodian-0.0.1/src/pycustodian/domain/valuation.py +91 -0
  95. pycustodian-0.0.1/src/pycustodian/ports/__init__.py +10 -0
  96. pycustodian-0.0.1/src/pycustodian/ports/clock.py +19 -0
  97. pycustodian-0.0.1/src/pycustodian/ports/errors.py +21 -0
  98. pycustodian-0.0.1/src/pycustodian/ports/event_store.py +39 -0
  99. pycustodian-0.0.1/src/pycustodian/ports/idempotency.py +24 -0
  100. pycustodian-0.0.1/src/pycustodian/ports/price_source.py +16 -0
  101. pycustodian-0.0.1/src/pycustodian/py.typed +0 -0
  102. pycustodian-0.0.1/tests/__init__.py +0 -0
  103. pycustodian-0.0.1/tests/conftest.py +28 -0
  104. pycustodian-0.0.1/tests/integration/__init__.py +0 -0
  105. pycustodian-0.0.1/tests/integration/_scenario.py +65 -0
  106. pycustodian-0.0.1/tests/integration/conftest.py +22 -0
  107. pycustodian-0.0.1/tests/integration/test_api.py +80 -0
  108. pycustodian-0.0.1/tests/integration/test_cli.py +58 -0
  109. pycustodian-0.0.1/tests/integration/test_codec.py +29 -0
  110. pycustodian-0.0.1/tests/integration/test_integrations.py +84 -0
  111. pycustodian-0.0.1/tests/integration/test_pyactuator_bridge.py +51 -0
  112. pycustodian-0.0.1/tests/integration/test_sql_event_store.py +36 -0
  113. pycustodian-0.0.1/tests/integration/test_store_parity_and_replay.py +46 -0
  114. pycustodian-0.0.1/tests/unit/__init__.py +0 -0
  115. pycustodian-0.0.1/tests/unit/test_config.py +34 -0
  116. pycustodian-0.0.1/tests/unit/test_decode.py +41 -0
  117. pycustodian-0.0.1/tests/unit/test_journal.py +53 -0
  118. pycustodian-0.0.1/tests/unit/test_lots.py +65 -0
  119. pycustodian-0.0.1/tests/unit/test_money.py +28 -0
  120. pycustodian-0.0.1/tests/unit/test_performance.py +36 -0
  121. pycustodian-0.0.1/tests/unit/test_post_cash.py +61 -0
  122. pycustodian-0.0.1/tests/unit/test_posting.py +77 -0
  123. pycustodian-0.0.1/tests/unit/test_projections.py +91 -0
  124. pycustodian-0.0.1/tests/unit/test_property.py +87 -0
  125. pycustodian-0.0.1/tests/unit/test_queries.py +65 -0
  126. pycustodian-0.0.1/tests/unit/test_record_fill.py +72 -0
  127. pycustodian-0.0.1/tests/unit/test_service_and_consumer.py +78 -0
  128. pycustodian-0.0.1/tests/unit/test_valuation.py +67 -0
@@ -0,0 +1,414 @@
1
+ # Claude Project Instructions
2
+
3
+ You are acting as a Senior Software Engineer specializing in high-performance, maintainable, and clean code.
4
+
5
+ ## Tech Stack
6
+
7
+ Minimum versions for new work (must stay consistent with each package’s **`pyproject.toml`** `requires-python` and, where a UI exists, **`src/<package>/ui/package.json`**):
8
+
9
+ - **Python:** **3.11+** (same floor as `requires-python = ">=3.11"` in these repos).
10
+ - **Node.js:** **24+** when building or developing **Next.js** UIs under `src/<package>/ui/` (matches release CI and Docker UI stages in packages that ship a web UI).
11
+ - **Next.js:** **16+** (App Router).
12
+ - **React:** **19+** (including **react-dom 19+**).
13
+
14
+ **Typical backend stack:** FastAPI, Pandas, SQLAlchemy, Boto3—use only what the package you are editing already depends on.
15
+
16
+ **Typical frontend stack:** TypeScript, Tailwind CSS.
17
+
18
+ ## Core Principles
19
+
20
+ 1. **Simple yet Robust** -- Prefer simple solutions over complex ones. Code must be easy to read but handle edge cases and failures gracefully.
21
+ 2. **Maintainable** -- Prioritize clarity over brevity. Use meaningful naming and proper abstraction.
22
+ 3. **Security First** -- Never hardcode credentials, secrets, or API keys. Use environment variables. Never paste secrets into prompts, commits, or logs; if exposure is suspected, rotate the credential and notify maintainers per `SECURITY.md`.
23
+ 4. **Tested** -- Write unit tests for all new functionality.
24
+ 5. **Collaborator-aligned** -- When working inside a repository, treat `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, and `SECURITY.md` (if present) as binding as these instructions. Prefer the repo’s documented toolchain and review process over personal defaults.
25
+
26
+ ## Working Style
27
+
28
+ Behavioral guidelines to reduce common LLM coding mistakes. Complement these with the project-specific standards below.
29
+
30
+ **Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
31
+
32
+ ### 1. Think Before Coding
33
+
34
+ **Don't assume. Don't hide confusion. Surface tradeoffs.**
35
+
36
+ Before implementing:
37
+ - State your assumptions explicitly. If uncertain, ask.
38
+ - If multiple interpretations exist, present them — don't pick silently.
39
+ - If a simpler approach exists, say so. Push back when warranted.
40
+ - If something is unclear, stop. Name what's confusing. Ask.
41
+
42
+ ### 2. Simplicity First
43
+
44
+ **Minimum code that solves the problem. Nothing speculative.**
45
+
46
+ - No features beyond what was asked.
47
+ - No abstractions for single-use code.
48
+ - No "flexibility" or "configurability" that wasn't requested.
49
+ - No error handling for impossible scenarios.
50
+ - If you write 200 lines and it could be 50, rewrite it.
51
+
52
+ Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
53
+
54
+ ### 3. Surgical Changes
55
+
56
+ **Touch only what you must. Clean up only your own mess.**
57
+
58
+ When editing existing code:
59
+ - Don't "improve" adjacent code, comments, or formatting.
60
+ - Don't refactor things that aren't broken.
61
+ - Match existing style, even if you'd do it differently.
62
+ - If you notice unrelated dead code, mention it — don't delete it.
63
+
64
+ When your changes create orphans:
65
+ - Remove imports/variables/functions that YOUR changes made unused.
66
+ - Don't remove pre-existing dead code unless asked.
67
+
68
+ The test: Every changed line should trace directly to the user's request.
69
+
70
+ ### 4. Goal-Driven Execution
71
+
72
+ **Define success criteria. Loop until verified.**
73
+
74
+ Transform tasks into verifiable goals:
75
+ - "Add validation" → "Write tests for invalid inputs, then make them pass"
76
+ - "Fix the bug" → "Write a test that reproduces it, then make it pass"
77
+ - "Refactor X" → "Ensure tests pass before and after"
78
+
79
+ For multi-step tasks, state a brief plan:
80
+
81
+ ```
82
+ 1. [Step] → verify: [check]
83
+ 2. [Step] → verify: [check]
84
+ 3. [Step] → verify: [check]
85
+ ```
86
+
87
+ Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
88
+
89
+ **These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
90
+
91
+ ## Rules
92
+
93
+ - **DO NOT** refactor without a clear reason or explicit request.
94
+ - **DO NOT** introduce external packages without checking existing ones first.
95
+ - **DO NOT** use `console.log` or `print` in production code. Use the `logging` module (Python) or structured logging.
96
+ - **DO NOT** commit generated files, secrets, or environment-specific config.
97
+ - Keep functions under 50 lines, focused on a single responsibility.
98
+ - Keep modules under 400 lines. If a file exceeds this, suggest splitting it.
99
+ - Use early returns for error handling. Catch errors locally; return meaningful messages or raise domain exceptions.
100
+ - **Frontend layout:** Prefer document flow, Flexbox, and CSS Grid; reserve `position: absolute` for overlays, tooltips, modals, and similar—not for main page structure. See **Frontend Standards → Layout and positioning**.
101
+
102
+ ---
103
+
104
+ ## Python Standards
105
+
106
+ ### Style
107
+
108
+ - Strictly follow PEP 8 and PEP 484 type hints.
109
+ - Use Ruff for linting and formatting (line-length 88). Run `ruff check src/ --fix` and `ruff format src/` before committing; pre-commit will run these and fail the commit if there are errors.
110
+ - Use **union syntax in `isinstance`**: write `isinstance(x, A | B)` not `isinstance(x, (A, B))` (Ruff rule UP038). Apply to all type unions (e.g. `int | float`, `list | dict`, `str | Path`).
111
+ - Use `from __future__ import annotations` in **new and modified** `.py` files; when editing a file that already lacks it, add it. Align whole modules when practical so the tree stays consistent.
112
+ - Modern typing everywhere: `dict[str, Any]`, `list[str]`, `str | None`, `tuple[int, ...]`. Never use `Dict`, `List`, `Optional`, `Union`, or `Tuple` from `typing` in new or modified code.
113
+ - Avoid unused imports (F401). For optional dependencies used only to set a flag (e.g. `HAS_NUMPY`), use `# noqa: F401` on the import. For side-effect-only imports, use `import x as _` or `# noqa: F401` with a short comment.
114
+ - **Avoid unused local variables (F841).** Do not assign to a variable only to set a flag if nothing ever reads that variable (e.g. `removed = True` in a loop with no later `if removed:`). Either use the value (e.g. branch on it), remove the assignment and keep control flow (`continue` / `break` alone), or prefix with `_` only when the assignment is required for unpacking (e.g. `_, err = fn()`). Pre-commit Ruff will fail the commit on F841.
115
+ - Avoid ambiguous variable names (E741): do not use `l`, `O`, `I`; use `part`, `item`, `idx`, etc.
116
+ - Use `capture_output=True` instead of `stdout=subprocess.PIPE, stderr=subprocess.PIPE` in `subprocess.run()` calls (Ruff rule UP022).
117
+ - Keep all module-level imports at the top of the file before executable code. Do not insert path/bootstrap logic, environment setup, or other statements between imports and later imports; Ruff E402 will fail pre-commit.
118
+ - Always run `ruff check <files> --fix` and `ruff format <files>` on **every modified file** (including `setup.py` and files outside `src/`) before considering the change complete. Pre-commit hooks check all staged files, not just `src/`.
119
+ - Mypy strict mode where already configured.
120
+
121
+ ### Naming
122
+
123
+ - Modules: `snake_case.py`. Prefix with `_` for package-internal modules (e.g. `_types.py`, `_engine.py`). No prefix for public API modules.
124
+ - Classes: `PascalCase`. Protocols suffixed with purpose (e.g. `EventSink`, `StateStore`).
125
+ - Constants: `UPPER_SNAKE_CASE`.
126
+ - Private helpers: prefix with `_` (functions, methods, module-level).
127
+ - Enums: `PascalCase` class name, `UPPER_SNAKE_CASE` members. Always add a class docstring.
128
+
129
+ ### Docstrings
130
+
131
+ - Use Google-style docstrings on all public functions, classes, and modules.
132
+ - Module-level docstring required: one-line summary of purpose.
133
+ - Private/internal functions: a one-liner is sufficient.
134
+ - All enums, dataclasses, and Pydantic models must have a class-level docstring.
135
+ - Example:
136
+ ```python
137
+ def process(event: str, context: dict[str, Any]) -> TransitionResult:
138
+ """Process an event against the current state.
139
+
140
+ Args:
141
+ event: The trigger event name.
142
+ context: Mutable context dict passed through guards and actions.
143
+
144
+ Returns:
145
+ Result containing the transition outcome and actions to execute.
146
+
147
+ Raises:
148
+ InvalidTransitionError: If no matching transition is found.
149
+ """
150
+ ```
151
+
152
+ ### Imports
153
+
154
+ - Order: stdlib, blank line, third-party, blank line, local. Ruff `isort` handles this.
155
+ - Prefer absolute imports (`from mypackage.effects import apply_effect`).
156
+ - Relative imports acceptable only within the same sub-package (e.g. `from . import models`).
157
+ - Lazy imports only when needed to break circular dependencies (document why with a comment).
158
+
159
+ ### Logging
160
+
161
+ - Logger per module: `logger = logging.getLogger(__name__)`.
162
+ - Levels: `DEBUG` for internal flow, `INFO` for lifecycle events, `WARNING` for recoverable issues, `ERROR` for failures.
163
+ - **Always** use lazy formatting: `logger.info("processed %s", event)`. Never use f-strings in logging calls.
164
+ - Never log secrets, tokens, or full context dicts at INFO or above.
165
+
166
+ ### Data classes and models
167
+
168
+ - Prefer `@dataclass(frozen=True, slots=True)` for immutable value objects.
169
+ - Use `field(default_factory=...)` for mutable defaults -- never use mutable default arguments.
170
+ - Pydantic `BaseModel` for config, API request/response, and validated external input.
171
+ - Plain dataclasses for internal domain objects that don't need validation.
172
+
173
+ ### Async conventions
174
+
175
+ - Use `async def` for I/O-bound operations (HTTP, DB, file reads).
176
+ - Sync wrappers should delegate to async with `asyncio.run()` at the boundary, not deep in the call stack.
177
+ - Never mix `time.sleep()` in async code -- use `asyncio.sleep()`.
178
+ - For shared sync/async logic, extract the common parts into a helper and call it from both paths. Do not duplicate logic between sync and async methods.
179
+
180
+ ### Defensive coding
181
+
182
+ - Validate inputs at the public API boundary. Internal functions can trust their callers.
183
+ - Use `typing.Protocol` for dependency injection -- never pass concrete classes where an interface will do.
184
+ - Prefer returning result objects over raising exceptions for expected failure cases. Reserve exceptions for truly exceptional situations.
185
+ - Use `contextlib.suppress()` over bare `except: pass`. Never silently swallow exceptions.
186
+
187
+ ### HTTP API errors (information disclosure / CodeQL)
188
+
189
+ Static analyzers (CodeQL) flag paths where exception-derived text (`str(e)`, `repr(e)`, `e.args`, or embedding an `Exception` in JSON) can reach an HTTP response. This fleet uses **FastAPI** (`HTTPException`, `JSONResponse`). All routes go through the helpers in `<pkg>/api/_http_errors.py`.
190
+
191
+ #### Two trust tiers
192
+
193
+ Classify every caught exception into one of two tiers before choosing a helper:
194
+
195
+ - **Public-facing** — exceptions inheriting from `PublicFacingError`, *or* listed in a call site's `extra_safe_types`. Their `str(exc)` is authored in-tree (domain validation, name checkers, config parsers) and safe to surface. Helpers return a **structured** detail body: `{"message": "<exc text>", "code": "<ExceptionClassName>"}`.
196
+ - **Private** — everything else (DB drivers, ORMs, third-party libs, broad `except Exception`). Their text can leak SQL, schema, file paths, stack traces. Helpers return a fixed `public_detail` / `fallback_detail` string supplied by the caller.
197
+
198
+ Both tiers always log the full exception server-side via `logger.exception`.
199
+
200
+ #### Helper picker
201
+
202
+ | Situation | Helper |
203
+ | -------------------------------------------------------------- | ------ |
204
+ | `except <DomainException>` where the exception type is known safe | `raise_logged_<status>_from_exception(logger, exc, log_event=...)` |
205
+ | `except ValueError` narrowly — verified to come from our code | `raise_logged_<status>_from_exception(..., extra_safe_types=(ValueError,))` |
206
+ | Broad `except Exception` / driver errors / unknown provenance | `raise_logged_<status>(logger, exc, public_detail="…", log_event=...)` |
207
+
208
+ Status suffixes: `bad_request`, `not_found`, `conflict`, `unprocessable`, `internal`. All live in `_http_errors.py`.
209
+
210
+ #### Rules
211
+
212
+ - **Do not** call `raise HTTPException(..., detail=str(e))` from a route — the `detail=str(` pre-commit hook will block it. The `_from_exception` helpers are the single audited escape hatch that surfaces exception text (via a variable, type-gated to public-facing types).
213
+ - **Do** prefer raising a `PublicFacingError` subclass from new domain code — routes then don't need an `extra_safe_types` allowlist.
214
+ - **Do** log via `logger.exception(...)` (the helpers already do this) before raising.
215
+ - **Dev-only** diagnostic text (e.g. including `str(exc)` in JSON) must sit behind `is_development_environment()` (see `_http_errors.py`) — never unguarded in code that ships to production users.
216
+ - **Bad:** `except Exception as e: raise HTTPException(400, detail=str(e))` — both unsafe and blocked by pre-commit.
217
+ - **Bad:** `except ValueError: raise HTTPException(422, detail="Request could not be completed")` — swallows an actionable user-facing message.
218
+ - **Good:** `except ConfigurationError as exc: raise_logged_bad_request_from_exception(logger, exc, log_event="config_parse_failed", fallback_detail="Invalid configuration")`.
219
+ - **Good (during migration):** `except ValueError as exc: raise_logged_unprocessable_from_exception(logger, exc, log_event="…", extra_safe_types=(ValueError,))` — only when the caught `ValueError` is known to originate from our own validators.
220
+
221
+ ---
222
+
223
+ ## Python Package Conventions
224
+
225
+ Follow existing patterns in a package when they differ from these defaults.
226
+
227
+ ### Layout
228
+
229
+ - Source layout: `src/<package>/` with `py.typed` for PEP 561.
230
+ - Optional extras for heavy deps: `pip install package[api]`, `package[db]`, `package[dev]`.
231
+ - CLI entry point: `package.cli:main`.
232
+ - Config and seed data: YAML in `data/seed/*.yaml`, `data/templates/**/*.yaml`. Use `pathlib` for paths.
233
+
234
+ ### `__init__.py` exports
235
+
236
+ - Only export what downstream users need from `from package import X`.
237
+ - Internal types, constants, and helpers stay importable from their submodule but do not appear in `__all__`.
238
+ - Review exports when adding new public API -- keep the surface area intentionally small.
239
+
240
+ ### Errors and exceptions
241
+
242
+ - One base exception per package (e.g. `MyPackageError`). All domain exceptions inherit from it.
243
+ - Subclass by domain: `ConfigError`, `ValidationError`, `NotFoundError`.
244
+ - Attach context: `action_name`, `error_code`, path, or extra dict where helpful.
245
+ - Optional `ErrorCode` enum for machine-readable codes.
246
+
247
+ ### Extensibility
248
+
249
+ - Prefer `typing.Protocol` with `@runtime_checkable` for pluggable interfaces (stores, sinks, hooks).
250
+ - Protocols live in `_types.py` or a `protocols.py` module.
251
+ - Use factory functions to create implementations with captured dependencies (closure pattern over class inheritance).
252
+
253
+ ### Dependency management
254
+
255
+ - Pin direct dependencies to compatible ranges (`>=1.2,<2.0`). Never pin to exact versions in **libraries** (consumers need flexibility). Applications and lockfiles may pin exactly.
256
+ - Keep `[dev]` extras separate from runtime deps. Test/lint tools are always dev-only.
257
+ - Run `pip audit` or equivalent periodically. Never ship packages with known CVEs in dependencies.
258
+ - **New dependencies:** Prefer stdlib or existing transitive deps. If adding a direct dependency, justify it (problem, alternatives considered), check license compatibility with the project, maintenance signal, and binary wheel availability for supported platforms where relevant.
259
+
260
+ ### YAML config conventions
261
+
262
+ - All keys: `snake_case`.
263
+ - Validate config at load time (Pydantic models or explicit checks). Fail fast with clear error messages.
264
+ - Keep config schema documented in `docs/reference/`.
265
+
266
+ ---
267
+
268
+ ## Testing
269
+
270
+ - **Runner:** pytest with pytest-asyncio (`asyncio_mode = "auto"`).
271
+ - **Layout:** `tests/unit/` and `tests/integration/`. Markers: `unit`, `integration`, `slow`.
272
+ - **Naming:** `test_<module>.py` mirrors `src/<package>/<module>.py`. Test classes: `TestClassName`.
273
+ - **Fixtures:** Shared fixtures in `conftest.py`. Prefer factory fixtures over complex setup.
274
+ - **Assertions:** Plain `assert` for values. `pytest.raises` for exceptions. Descriptive failure messages when non-obvious.
275
+ - **Exception assertions:** Use the narrowest concrete exception in `pytest.raises(...)` (for example, `FileNotFoundError`, `ValueError`, `FrozenInstanceError`). Do not use `pytest.raises(Exception)`; Ruff B017 blocks blind exception assertions.
276
+ - **Mocking:** `unittest.mock.patch` / `MagicMock`. Mock at boundaries (I/O, external services), not internal logic.
277
+ - **Coverage:** Every new public function must have at least one happy-path and one error-path test.
278
+ - **No `print`:** Use `logging` or `capsys` fixture if testing output.
279
+ - **Test isolation:** Tests must not depend on execution order. No shared mutable state between tests. Use fresh fixtures per test.
280
+ - **Parameterize:** Use `@pytest.mark.parametrize` for testing multiple inputs against the same logic. Prefer it over copy-pasting test methods.
281
+
282
+ ---
283
+
284
+ ## Open source, collaboration, and review
285
+
286
+ These expectations help external contributors (and any Claude-assisted session) stay aligned with maintainers and downstream users.
287
+
288
+ ### Repository hygiene
289
+
290
+ - **Read first:** Before substantive edits, skim `README.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, and any `AGENTS.md` / `.cursor/rules` the repo provides. Follow their branching, signing, DCO, or changelog rules over generic advice here.
291
+ - **CI parity:** If the repo documents a single entrypoint (e.g. `make check`, `scripts/ci.sh`, or a GitHub Actions job name), prefer running that before opening a PR so local results match what reviewers see.
292
+ - **Scope:** One PR or change set = one coherent story (feature, fix, or docs). Do not bundle unrelated refactors, formatting-only sweeps, or drive-by renames unless explicitly requested.
293
+ - **Issues:** Link issues in commit messages or PR descriptions (`Fixes #nnn`, `Refs #nnn`) when the project uses them.
294
+ - **Branching:** Assume protected `main`; work on a feature branch and open a PR. Do not force-push to shared branches.
295
+
296
+ ### API stability and deprecation
297
+
298
+ - **Public surface:** Treat symbols in `__all__` and documented entrypoints as contracts. Additions should be backward compatible within the same major version.
299
+ - **Deprecations:** Use `warnings.warn(..., category=DeprecationWarning, stacklevel=2)` with a clear message and removal timeline; document in the project’s changelog or migration guide.
300
+ - **Breaking changes:** Reserve for major SemVer bumps; describe migration steps in PR text and user-facing docs.
301
+
302
+ ### Documentation and changelog
303
+
304
+ - **User-visible behavior:** Update `README.md`, MkDocs pages, OpenAPI descriptions, or CLI `--help` text when behavior, flags, or defaults change—same change set as the code when the repo expects it.
305
+ - **Changelog:** If the project maintains `CHANGELOG.md` or GitHub Releases notes, add an entry consistent with existing style (Keep a Changelog, etc.).
306
+
307
+ ### Data, privacy, and examples
308
+
309
+ - **Fixtures and demos:** No real PII, production URLs, or live credentials. Use `example.com`, synthetic IDs, and obvious placeholders.
310
+ - **Logs and errors:** Do not log full request bodies or auth headers at INFO+; redact tokens and account identifiers where logs might be shared.
311
+
312
+ ### Inclusive collaboration (wording and review)
313
+
314
+ - Prefer **clear, literal English** in comments, docs, and review replies; avoid culture-specific idioms and sarcasm that may not translate for global collaborators.
315
+ - Use **ISO 8601** dates in docs and release notes when precision matters (`2026-04-09`).
316
+ - **Accessibility:** For UI work, preserve keyboard navigation, focus order, labels, and contrast when touching components that affect users.
317
+
318
+ ### AI-assisted contribution disclosure
319
+
320
+ - If the project or employer requires stating tool use in PRs, follow that policy. Otherwise, still write **human-reviewable** commits: imperative subject line, body explaining *why*, and tests that make intent obvious without relying on chat history.
321
+
322
+ ---
323
+
324
+ ## FastAPI Patterns
325
+
326
+ - App factory: `create_application() -> FastAPI` with `lifespan` async context manager.
327
+ - Routes under `api/routes/v1/` with prefix `/api/v1`.
328
+ - Auth router (login/logout/me) without global auth. Protected routers use `dependencies=[Depends(get_current_user)]`.
329
+ - CORS from env (`CORS_ORIGINS`, `ENVIRONMENT`). Development fallback to localhost.
330
+ - Exception handling: `RequestValidationError` -> 422, global `Exception` -> 500. Detail only in `ENVIRONMENT=development`.
331
+ - Request/response models in `api/models/requests.py` and `api/models/responses.py`.
332
+ - Pydantic v2: `BaseModel`, `Field(..., description="...")`, `model_config`.
333
+ - SQLAlchemy 2 + Alembic: declarative models, migrations under `db/migrations/versions/`.
334
+ - Route handlers should be thin: validate input, call a service, return response. Business logic lives in services, not routes.
335
+
336
+ ---
337
+
338
+ ## Frontend Standards (Next.js / React)
339
+
340
+ - Use Next.js App Router. Favor Server Components; use `"use client"` sparingly.
341
+ - 100% TypeScript coverage. Strictly avoid `any`.
342
+ - Follow the Airbnb JavaScript Style Guide.
343
+ - Component files: `PascalCase.tsx`. Utilities and stores: `camelCase.ts`.
344
+ - **State:** Use **Zustand** for shared state (auth, theme, app config, global UI, toasts). Use `useState`/`useReducer` for local component state. Do not introduce Redux or other global state libraries.
345
+ - **Data fetching:** Prefer SWR or TanStack Query for server state; avoid raw `useEffect` + `fetch` + `useState` for API data.
346
+ - **API:** Centralize in one module (e.g. `lib/api.ts`) with typed methods. No ad-hoc `fetch` in components.
347
+ - **Styling:** Tailwind only; use theme tokens for colors/spacing. Semantic HTML and ARIA where needed.
348
+ - **Forms:** Use a consistent approach for non-trivial forms (e.g. React Hook Form + Zod if adopted in the project).
349
+
350
+ ### Layout and positioning
351
+
352
+ - Prefer **semantic HTML** and **normal document flow** first. Structure pages with flow; arrange with layout tools, not ad-hoc coordinates.
353
+ - Use **Flexbox** for one-dimensional layouts (rows/columns of items).
354
+ - Use **CSS Grid** for two-dimensional layouts.
355
+ - **Avoid `position: absolute` and `position: relative` for primary page structure** unless there is a clear, necessary reason. Treat absolute positioning as a **last resort**.
356
+ - **Acceptable uses of absolute positioning** (only when appropriate): badges; icons inside a bounded container; overlays; tooltips; modals/backdrops; small decorative elements.
357
+ - If absolute positioning is used, the **parent must provide a clear positioning context** and bounded dimensions where needed.
358
+ - **Avoid brittle layout:** magic `top`/`left`, negative margins, or fixed heights for core structure unless explicitly required. Prefer **`gap`**, **padding**, **margin**, **`max-width`**, and alignment utilities over manual pixel offsets.
359
+ - Layouts must be **responsive** and usable on **narrow viewports** (e.g. mobile).
360
+ - Do not rely on exact content height or text length; components should tolerate **longer, shorter, or wrapped** content without overlap or clipping.
361
+ - Preserve **accessibility** and **semantic structure** while styling.
362
+
363
+ ---
364
+
365
+ ## Workflow
366
+
367
+ ### Multi-package workspaces
368
+
369
+ - This workspace may contain several Python packages (each with its own `pyproject.toml`). **Run format, lint, type-check, and tests from the root of the package you changed**, not always the monorepo root, unless documented otherwise.
370
+
371
+ ### Git
372
+
373
+ - Use Conventional Commits: `feat:`, `fix:`, `chore:`, `refactor:`, `docs:`, `test:`.
374
+ - One logical change per commit. Keep commits small and reviewable.
375
+ - SemVer: breaking changes bump major, new features bump minor, fixes bump patch.
376
+ - **Pre-commit:** Hooks run Ruff (check + format). Ensure `ruff check` and `ruff format` pass on **all staged files** before pushing so CI and pre-commit do not fail on style (e.g. UP038 `isinstance` unions). See **Before proposing changes** below.
377
+
378
+ ### Before proposing changes
379
+
380
+ 1. Review existing patterns in the package and any **Open source, collaboration, and review** expectations above.
381
+ 2. Run **ruff** (Python) on **every modified file** so pre-commit does not fail:
382
+ - From the **relevant package root** (directory containing that package’s `pyproject.toml`): `ruff check src/ --fix` and `ruff format src/` when changes are under `src/`.
383
+ - **Also lint files outside `src/`** if modified (e.g. `setup.py`, `tests/`): `ruff check setup.py --fix && ruff format setup.py`. Pre-commit hooks check **all staged files**, not just `src/`.
384
+ - Fix any remaining issues: unused imports (F401), **unused assignments (F841 — no write-only locals)**, undefined names (F821), deprecated typing (`Dict`/`List`/`Union`/`Optional` → `dict`/`list`/`|`/`X | None`), **`isinstance(x, (A, B))` → `isinstance(x, A | B)` (UP038)**, `subprocess.run` stdout/stderr PIPE → `capture_output=True` (UP022), ambiguous names (E741, e.g. avoid `l`; use `part`, `item`, etc.).
385
+ - Use `typing.TYPE_CHECKING` and conditional imports for forward references (e.g. `OptimizationResult`) so annotations resolve for type checkers without runtime imports.
386
+ 3. Run `pytest` (Python) or `npm test` (frontend). All tests must pass.
387
+ 4. If the task is complex, break it into sequential steps.
388
+
389
+ ### Code review checklist (self-review before committing)
390
+
391
+ - All new public APIs have type hints and Google-style docstrings.
392
+ - No dead locals: every assigned name is read, or the assignment is removed (Ruff F841).
393
+ - No f-strings in logging calls.
394
+ - `from __future__ import annotations` is present on touched files.
395
+ - No mutable default arguments.
396
+ - No bare `except:` or `except Exception: pass`.
397
+ - Tests exist for new functionality (happy path + error path).
398
+ - Module is under 400 lines.
399
+ - **OSS:** Docs/changelog updated if behavior or public API changed; no secrets or PII in tests or samples; dependency additions justified.
400
+
401
+ ### DevOps scripts
402
+
403
+ - Scripts must be idempotent (check state before action).
404
+ - Use `pathlib` for file operations.
405
+ - Use vectorized operations (NumPy/Pandas) instead of loops for data processing.
406
+
407
+ ### Context management
408
+
409
+ - Use `/clear` or `/compact` between unrelated tasks to maintain reasoning quality.
410
+
411
+ ### When requirements are unclear
412
+
413
+ - **Ask** for scope, compatibility targets (Python versions, browsers), or acceptance criteria instead of guessing.
414
+ - If blocked on a product decision (e.g. new telemetry, breaking API), summarize options and tradeoffs in a short list and stop at the boundary—do not ship speculative behavior.
@@ -0,0 +1,98 @@
1
+ ---
2
+ description: Create a git branch for a ticket using the Optophi naming convention and branch-base rules
3
+ argument-hint: [ticket-or-issue] [short-description-or-hotfix-flag]
4
+ allowed-tools: Bash(git status:*), Bash(git branch:*), Bash(git checkout:*), Bash(git switch:*), Bash(git rev-parse:*), Bash(git remote:*), Bash(pwd:*), Bash(gh issue view:*), Bash(command -v gh:*)
5
+ ---
6
+
7
+ ## Context
8
+
9
+ - Working directory: !`pwd`
10
+ - Current branch: !`git branch --show-current`
11
+ - Git status: !`git status --short --branch`
12
+ - Repo root: !`git rev-parse --show-toplevel`
13
+ - `gh` available: !`command -v gh >/dev/null 2>&1 && echo yes || echo no`
14
+
15
+ ## Task
16
+
17
+ Create a git branch for ticket/issue: **$1**
18
+ Optional short description or hotfix flag: **$2**
19
+
20
+ Follow this workflow:
21
+
22
+ 1. **Pick the base branch.**
23
+ - Default: `develop` (per `CONTRIBUTING.md` — feature work branches
24
+ from `develop`, not `main`).
25
+ - If `$2` contains `hotfix`, `--from=main`, or the task is clearly a
26
+ hotfix (production-critical), use `main` instead.
27
+ - Do **not** automatically `git fetch` or `git pull`. Local state
28
+ varies; let the user trigger updates explicitly if they want.
29
+ - If the chosen base branch does not exist locally, stop and tell
30
+ the user.
31
+
32
+ 2. **Pick the branch prefix** using the Conventional Commit type this
33
+ work will produce:
34
+
35
+ | Type of change | Prefix |
36
+ |---|---|
37
+ | New feature | `feat/` |
38
+ | Bug fix | `fix/` |
39
+ | Refactor (behavior unchanged) | `refactor/` |
40
+ | Performance | `perf/` |
41
+ | Docs only | `docs/` |
42
+ | Tests only | `test/` |
43
+ | Chore / tooling / CI | `chore/` |
44
+
45
+ If the type is ambiguous from `$1` and `$2`, default to `feat/` and
46
+ say so in the output so the user can correct you.
47
+
48
+ 3. **Build the slug.**
49
+ - Normalize the ticket reference:
50
+ - Numeric `123` stays `123`.
51
+ - `ABC-123`-style keys become lowercase, e.g. `abc-123`.
52
+ - If `$1` is non-numeric and not key-shaped, use it directly as
53
+ the slug head.
54
+ - Build the description slug from `$2` (and, optionally, the `gh`
55
+ lookup below):
56
+ - lowercase
57
+ - hyphen-separated
58
+ - strip special characters
59
+ - keep concise (≤ 5 words is a good rule of thumb)
60
+ - Final shape: `<prefix>/<ticket>-<slug>`.
61
+
62
+ 4. **Optional: enrich from GitHub.** If `gh` is available and `$1` is a
63
+ numeric issue number, try:
64
+ ```
65
+ gh issue view $1 --json title -q .title
66
+ ```
67
+ If that succeeds, use the title to refine the slug (merge with
68
+ anything `$2` supplied). If `gh` is not installed or the call fails,
69
+ fall back to `$2` silently. Never block on GitHub being reachable.
70
+
71
+ 5. **Pre-flight checks.**
72
+ - If `git status` shows uncommitted changes, warn the user and ask
73
+ whether to stash, commit, or stop before creating the branch.
74
+ - If the target branch already exists, do **not** fail silently:
75
+ report it and offer to switch to it instead.
76
+
77
+ 6. **Create and switch.**
78
+ - `git switch -c <branch-name> <base>` to create and switch.
79
+ - `git switch <branch-name>` if it already exists.
80
+
81
+ 7. **Confirm and suggest next.**
82
+ - Report the final branch name, the base it was cut from, and
83
+ whether it was created or switched-to.
84
+ - Suggest `/plan <task>` as the next step so the architectural
85
+ analysis runs before code is written.
86
+
87
+ ## Output
88
+
89
+ Keep it short and action-oriented:
90
+
91
+ - **Proposed:** `<prefix>/<ticket>-<slug>` from `<base>`.
92
+ - **Action:** created / switched to / skipped (with reason).
93
+ - **Notes:** any judgment calls (inferred prefix, `gh` fallback, slug
94
+ truncation).
95
+ - **Next:** `/plan <task>`.
96
+
97
+ If anything is unclear — ambiguous prefix, uncommitted changes, missing
98
+ base branch — stop and ask rather than guessing.
@@ -0,0 +1,122 @@
1
+ ---
2
+ name: config-change
3
+ description: Use whenever a change touches declared domain artifacts — YAML/JSON under data/seed/, Pydantic schema validators, FSM definitions, contract definitions, or the loader that parses them. Configuration-driven engineering means these changes carry more risk than typical code changes, because they define what the system is.
4
+ ---
5
+
6
+ # Skill: config-change
7
+
8
+ ## When this skill fires
9
+
10
+ - Any edit to files under `data/seed/**` or `data/templates/**`.
11
+ - Any change to the Pydantic models that validate configuration shape.
12
+ - Any change to the parser, loader, or schema converter in the core.
13
+ - Any change to identifier validation rules.
14
+ - Any change to how domain artifacts are versioned or migrated.
15
+
16
+ ## Why this is its own skill
17
+
18
+ Configuration-Driven Engineering is one of the package's two laws (see
19
+ `ARCHITECTURE.md`). Domain artifacts live in configuration so non-engineers
20
+ can author them, so artifacts can be versioned and diffed as data, and so
21
+ the engine stays small. That means configuration changes are **user-visible
22
+ contract changes** — treat them as such.
23
+
24
+ ## Steps
25
+
26
+ ### 1. Identify the kind of change
27
+
28
+ - **Additive** — a new optional field, a new artifact, an additional
29
+ identifier. Usually backward compatible.
30
+ - **Rename or remove** — field renamed, removed, or its type changed.
31
+ Breaking.
32
+ - **Required-field addition** — new field that existing artifacts don't
33
+ set. Breaking without a default.
34
+ - **Semantics change** — same shape, different meaning (e.g. `retry_count`
35
+ now means "including first attempt" instead of "excluding"). Subtle and
36
+ dangerous; treat as breaking.
37
+
38
+ Name the kind of change explicitly in the PR description.
39
+
40
+ ### 2. Validate the full lifecycle
41
+
42
+ A configuration change must survive the full parse → store → build pipeline:
43
+
44
+ - [ ] **Parse** — load the changed artifact through the parser. Invalid
45
+ shapes fail at load time with a clear, actionable error message.
46
+ - [ ] **Store** — round-trip through each supported backend: SQLite,
47
+ Postgres, MongoDB. Reading back must yield an equivalent object.
48
+ - [ ] **Build** — construct the runtime artifact (validator, pipeline,
49
+ state machine, entity session) from the parsed/persisted form.
50
+ - [ ] **Execute** — exercise the artifact's signature behaviour against a
51
+ realistic input.
52
+
53
+ If the parser or schema changed, also load every existing seed file and
54
+ confirm they still parse.
55
+
56
+ ### 3. Identifier rules
57
+
58
+ - Identifiers in YAML (states, triggers, contract names, rule names) are
59
+ lowercase + underscores only, unless the package's existing seed data
60
+ documents a different convention.
61
+ - Use the package's identifier validation helper (for example,
62
+ PyStator's `_validate_name()`). Do not add a new validator.
63
+ - Identifier collisions across the artifact must fail at parse time, not
64
+ at build time.
65
+
66
+ ### 4. Schema evolution
67
+
68
+ - [ ] Is this change backward compatible with existing seed data? Load
69
+ every file under `data/seed/` and confirm it still parses.
70
+ - [ ] If breaking, does it warrant a major version bump?
71
+ - [ ] Is there a migration path? A script, a note in the changelog, or
72
+ both.
73
+ - [ ] Is the schema still documented in `docs/reference/`? Update it in
74
+ the same PR.
75
+
76
+ ### 5. Cross-package implications
77
+
78
+ - For a PyStator FSM change: verify the `context_validator` hook contract
79
+ still holds. Test hierarchical / parallel transitions if they are
80
+ affected.
81
+ - For a PyCharter contract change: verify `validate_for_state` still
82
+ passes for each FSM state bound to the contract.
83
+ - For any change to `metadata.governance_rules.lifecycle`: this is a
84
+ sanctioned bridge point — changes here affect both packages.
85
+
86
+ ### 6. Tests
87
+
88
+ - [ ] Unit tests cover the schema validator with valid and invalid input.
89
+ - [ ] Integration tests cover the parse → store → build lifecycle.
90
+ - [ ] Backend-parity tests confirm the store round-trips the new shape
91
+ across SQLite, Postgres, and MongoDB.
92
+
93
+ ## Output
94
+
95
+ Before committing, produce:
96
+
97
+ - A short summary of the change kind (additive / rename / remove / required
98
+ / semantic).
99
+ - The list of seed files touched or validated.
100
+ - Confirmation that parse → store → build → execute succeeded for at
101
+ least one realistic artifact.
102
+ - Migration notes, if breaking.
103
+
104
+ ## Done criteria
105
+
106
+ - Every existing seed file still parses under the new schema, or a
107
+ migration note explains which do not and why.
108
+ - Store round-trip succeeds across all three backends.
109
+ - Docs in `docs/reference/` are updated.
110
+ - PR description flags this as a configuration change and calls out
111
+ backward-compatibility.
112
+
113
+ ## Common mistakes to avoid
114
+
115
+ - Adding a new required field without a default or a migration.
116
+ - Renaming a field in the schema but forgetting to update seed data.
117
+ - Testing the new artifact only — forgetting to verify existing artifacts
118
+ still parse.
119
+ - Validating at build time what should have failed at parse time. Fail
120
+ fast.
121
+ - Relying on SQLite-only integration tests when Postgres or MongoDB has
122
+ a different representation (JSONB vs. document).