Skip to content

feat: generalize versioning to VersionSnapshot[T] for all entity types#1155

Open
Aureliolo wants to merge 13 commits intomainfrom
feat/versioning-generalization
Open

feat: generalize versioning to VersionSnapshot[T] for all entity types#1155
Aureliolo wants to merge 13 commits intomainfrom
feat/versioning-generalization

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

Migrates bespoke WorkflowDefinitionVersion to generic VersionSnapshot[T] and extends versioning infrastructure to 5 entity types total.

#1131: Migrate workflow versioning to generic VersionSnapshot[WorkflowDefinition]

  • Deleted bespoke WorkflowDefinitionVersion model, WorkflowVersionRepository protocol, SQLiteWorkflowVersionRepository
  • Migrated schema from flattened columns to generic (entity_id, version, content_hash, snapshot) format
  • Updated compute_diff() to accept VersionSnapshot[WorkflowDefinition], unwrapping at top level
  • Replaced manual build_version_snapshot() with VersioningService.snapshot_if_changed() at all 4 mutation sites
  • Benefits: content-hash dedup, concurrent-write conflict resolution, single implementation

#1132: Add versioning to EvaluationConfig, BudgetConfig, CompanyStructure

  • Schema: 3 new version tables (evaluation_config_versions, budget_config_versions, company_versions)
  • Backend + protocol: 3 new SQLiteVersionRepository instances with properties
  • Mutation wiring: OrgMutationService._snapshot_budget_config() called inside _org_lock after budget mutations
  • API: 3 new controllers with list/get version history endpoints

#1133: Add versioning to Role Catalog definitions

  • Design decision: per-role versioning (built-in catalog is immutable code, only custom roles need versioning)
  • Schema: role_versions table
  • Backend + protocol: SQLiteVersionRepository[Role] wired

Test plan

  • 15,995 tests passing (15,986 unit + 9 skipped platform-specific)
  • New test_entity_version_repos.py: round-trip serialization for all 5 entity types + content-hash dedup
  • New test_config_version_controllers.py: 11 tests covering list/get/pagination/404 for all 3 new controllers
  • Updated test_diff.py, test_workflow_versions.py, test_migrations.py, test_protocol.py, fakes

Review coverage

Pre-reviewed by 13 agents, 3 findings addressed:

  1. Race condition: moved _snapshot_budget_config() inside _org_lock
  2. Exception gap: broadened catch to handle resolver errors (not just PersistenceError)
  3. Missing tests: added controller tests for all 3 new version endpoints

Closes #1131
Closes #1132
Closes #1133

Copilot AI review requested due to automatic review settings April 8, 2026 17:56
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cc997e3d-3519-4a6a-8c6a-b0d0da507e20

📥 Commits

Reviewing files that changed from the base of the PR and between f264d94 and f410cff.

📒 Files selected for processing (1)
  • src/synthorg/engine/workflow/diff.py
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations

Use except A, B: (no parentheses) for PEP 758 except syntax in Python 3.14

Type hints: all public functions, mypy strict mode

Docstrings: Google style, required on public classes/functions (enforced by ruff D rules)

Create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.

For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves.

Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time.

Use @computed_field for derived values instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens).

Use NotBlankStr (from core.types) for all identifier/name fields -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators.

Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.

Line length: 88 characters (ruff enforced)

Functions: < 50 lines, files < 800 lines

Handle errors explicitly, never silently swallow

Validate at system boundaries (user input, external APIs, config files)

All error paths must log at WARNING or ERROR with context before raising

All state transitions must log at INFO level

Use DEBUG logging for obj...

Files:

  • src/synthorg/engine/workflow/diff.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)

Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, observability/http_handler.py, and observability/otlp_handler.py may use stdlib logging and print(..., file=sys.stderr).

Always use constant event names from the domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool, etc.). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT

Use structured kwargs for logging: always logger.info(EVENT, key=value) -- never logger.info("msg %s", val)

Files:

  • src/synthorg/engine/workflow/diff.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use vendor-agnostic names everywhere in project-owned code, docstrings, comments, tests, and config examples. Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.). Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, or large/medium/small aliases.

Files:

  • src/synthorg/engine/workflow/diff.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/engine/workflow/diff.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. The design spec is the starting point for architecture, data models, and behavior.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: When implementation deviates from the spec (better approach found, scope evolved, etc.), alert the user and explain why -- user decides whether to proceed or update the spec. Do NOT silently diverge from the design specification.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding starts.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: At every phase of planning and implementation, be critical and actively look for ways to improve the design. Surface improvements as suggestions, not silent changes -- user decides.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: Prioritize issues by dependency order, not priority labels -- unblocked dependencies come first.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: Pure data models, enums, and re-exports do NOT need logging
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: Use `pytest-xdist` via `-n 8` -- ALWAYS include `-n 8` when running pytest locally, never run tests sequentially. CI uses `-n auto`.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: Git commits: `<type>: <description>` -- types: feat, fix, refactor, docs, test, chore, perf, ci. Enforced by commitizen (commit-msg hook). Signed commits required on `main`.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: Use branch naming convention: `<type>/<slug>` from main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint (Dockerfile linting), golangci-lint + go vet (CLI), no-em-dashes, no-redundant-timeout, eslint-web (web dashboard)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: Pre-push hooks enforce: mypy type-check (affected modules only) + pytest unit tests (affected modules only) + golangci-lint + go vet + go test (CLI) + eslint-web (web dashboard). Foundational module changes (core, config, observability) or conftest changes trigger full runs.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: NEVER use `cd` in Bash commands. The working directory is already set to the project root. Use absolute paths or run commands directly. Exception: `bash -c "cd <dir> && <cmd>"` is safe (runs in a child process).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: NEVER use Bash to write or modify files. Use the Write or Edit tools instead. Do not use `cat >`, `cat << EOF`, `echo >`, `echo >>`, `sed -i`, `python -c "open(...).write(...)"`, or `tee` to create or modify files.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: After finishing an issue implementation, create a feature branch (`<type>/<slug>`), commit, and push -- do NOT create a PR automatically. Do NOT leave work uncommitted on main.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: NEVER create a PR directly with `gh pr create`. ALWAYS use `/pre-pr-review` to create PRs. For trivial/docs-only changes: `/pre-pr-review quick` skips agents but still runs automated checks.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:04:40.928Z
Learning: When review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes), fix them all. No deferring, no "out of scope" skipping.
🔇 Additional comments (4)
src/synthorg/engine/workflow/diff.py (4)

14-23: LGTM!

The import changes correctly bring in WorkflowDefinition and VersionSnapshot to support the migration from the bespoke WorkflowDefinitionVersion to the generic VersionSnapshot[WorkflowDefinition] pattern.


167-222: LGTM!

The compute_diff function correctly:

  1. Updates the signature to accept VersionSnapshot[WorkflowDefinition]
  2. Validates entity_id match between snapshots first
  3. Adds per-snapshot integrity checks ensuring snapshot.id == entity_id (addressing the previously identified concern)
  4. Unwraps .snapshot to pass WorkflowDefinition to internal diff helpers
  5. Uses old.entity_id for definition_id in the result

The validation order is appropriate — checking entity match first, then individual snapshot integrity.


295-332: LGTM!

The _diff_nodes helper signature update to accept WorkflowDefinition aligns with the caller change in compute_diff which now passes old.snapshot and new.snapshot directly. Internal logic remains unchanged as it operates on the .nodes attribute present on WorkflowDefinition.


394-468: LGTM!

The _diff_edges and _diff_metadata helper signatures are correctly updated to accept WorkflowDefinition directly, consistent with the compute_diff refactoring. Both functions access attributes (edges, name, description, workflow_type) that exist on WorkflowDefinition as confirmed by the relevant code snippet from definition.py.


Walkthrough

The PR replaces bespoke workflow-versioning with a generic VersionSnapshot[T]/VersionRepository[T] approach: it removes the WorkflowDefinitionVersion model and its repository/protocol, introduces generic SQLiteVersionRepository[T] usage for workflow snapshots, updates the SQLite schema to entity-keyed snapshot tables (content_hash + snapshot) and adds tables for evaluation_config, budget_config, company, and role, switches snapshot persistence to VersioningService.snapshot_if_changed(), adds controllers for the new versioned entities, and adds a migration check for legacy workflow version tables.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: generalize versioning to VersionSnapshot[T] for all entity types' clearly describes the main change: migrating from bespoke WorkflowDefinitionVersion to a generic VersionSnapshot pattern across multiple entity types.
Description check ✅ Passed The description provides a comprehensive summary aligned with the changeset: it documents migration of WorkflowDefinitionVersion to generic VersionSnapshot[T], addition of versioning for EvaluationConfig/BudgetConfig/CompanyStructure/Role, and test coverage improvements.
Linked Issues check ✅ Passed The PR fulfills all coding requirements from #1131, #1132, and #1133: deleted bespoke workflow version files, added generic SQLiteVersionRepository instances, wired mutation snapshots via VersioningService, created new controllers, updated schema, and added comprehensive tests.
Out of Scope Changes check ✅ Passed All changes are in-scope: they either implement versioning generalization (#1131, #1132, #1133), refactor code to use the new pattern, update tests, or document the changes. No unrelated functionality was introduced.
Docstring Coverage ✅ Passed Docstring coverage is 53.33% which is sufficient. The required threshold is 40.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Preview Deployment

URL: https://pr-1155.synthorg-pr-preview.pages.dev

Built from commit f410cff

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 3 package(s) with unknown licenses.
See the Details below.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA f410cff.
Ensure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice.

License Issues

web/package-lock.json

PackageVersionLicenseIssue Type
react19.2.5NullUnknown License
react-dom19.2.5NullUnknown License
side-channel-list1.0.1NullUnknown License
Allowed Licenses: MIT, MIT-0, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, MPL-2.0, PSF-2.0, Unlicense, 0BSD, CC0-1.0, CC-BY-3.0, CC-BY-4.0, Python-2.0, Python-2.0.1, LicenseRef-scancode-free-unknown, LicenseRef-scancode-protobuf, LicenseRef-scancode-google-patent-license-golang, ZPL-2.1, LGPL-2.0-only, LGPL-2.0-or-later, LGPL-2.1-only, LGPL-2.1-or-later, LGPL-3.0-only, LGPL-3.0-or-later, BlueOak-1.0.0, OFL-1.1
Excluded from license check: pkg:pypi/mem0ai@1.0.9, pkg:pypi/numpy@2.4.4, pkg:pypi/qdrant-client@1.17.1, pkg:pypi/posthog@7.9.12, pkg:pypi/aiohttp@3.13.5, pkg:pypi/cyclonedx-python-lib@11.7.0, pkg:pypi/fsspec@2026.3.0, pkg:pypi/griffelib@2.0.2, pkg:pypi/grpcio@1.80.0, pkg:pypi/charset-normalizer@3.4.6, pkg:pypi/wrapt@2.1.2, pkg:npm/@img/sharp-wasm32@0.33.5, pkg:npm/@img/sharp-win32-ia32@0.33.5, pkg:npm/@img/sharp-win32-x64@0.33.5, pkg:golang/github.com/golangci/golangci-lint/v2@2.11.3, pkg:golang/github.com/denis-tingaikin/go-header@0.5.0, pkg:golang/github.com/ldez/structtags@0.6.1, pkg:golang/github.com/leonklingele/grouper@1.1.2, pkg:golang/github.com/xen0n/gosmopolitan@1.3.0, pkg:golang/github.com/alfatraining/structtag@1.0.0, pkg:golang/github.com/fatih/structtag@1.2.0, pkg:npm/json-schema-typed@8.0.2, pkg:npm/victory-vendor@37.3.6, pkg:pypi/scikit-learn@1.8.0, pkg:pypi/torch@2.11.0, pkg:pypi/cuda-bindings@13.2.0, pkg:pypi/cuda-pathfinder@1.5.0, pkg:pypi/cuda-toolkit@13.0.2, pkg:pypi/nvidia-cublas@13.1.0.3, pkg:pypi/nvidia-cuda-cupti@13.0.85, pkg:pypi/nvidia-cuda-nvrtc@13.0.88, pkg:pypi/nvidia-cuda-runtime@13.0.96, pkg:pypi/nvidia-cudnn-cu13@9.19.0.56, pkg:pypi/nvidia-cufft@12.0.0.61, pkg:pypi/nvidia-cufile@1.15.1.6, pkg:pypi/nvidia-curand@10.4.0.35, pkg:pypi/nvidia-cusolver@12.0.4.66, pkg:pypi/nvidia-cusparse@12.6.3.3, pkg:pypi/nvidia-cusparselt-cu13@0.8.0, pkg:pypi/nvidia-nccl-cu13@2.28.9, pkg:pypi/nvidia-nvjitlink@13.0.88, pkg:pypi/nvidia-nvshmem-cu13@3.4.5, pkg:pypi/nvidia-nvtx@13.0.85

OpenSSF Scorecard

PackageVersionScoreDetails
npm/@emnapi/core 1.9.1 🟢 3.8
Details
CheckScoreReason
Code-Review⚠️ 0Found 0/30 approved changesets -- score normalized to 0
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Maintained🟢 1021 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 10
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Security-Policy⚠️ 0security policy file not detected
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Packaging🟢 10packaging workflow detected
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/@emnapi/runtime 1.9.1 🟢 3.8
Details
CheckScoreReason
Code-Review⚠️ 0Found 0/30 approved changesets -- score normalized to 0
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Maintained🟢 1021 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 10
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Security-Policy⚠️ 0security policy file not detected
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Packaging🟢 10packaging workflow detected
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/@emnapi/wasi-threads 1.2.0 🟢 3.8
Details
CheckScoreReason
Code-Review⚠️ 0Found 0/30 approved changesets -- score normalized to 0
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Maintained🟢 1021 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 10
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Security-Policy⚠️ 0security policy file not detected
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Packaging🟢 10packaging workflow detected
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/axios 1.15.0 🟢 6.9
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 18 issue activity found in the last 90 days -- score normalized to 10
Security-Policy🟢 10security policy file detected
Code-Review🟢 5Found 13/26 approved changesets -- score normalized to 5
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Binary-Artifacts🟢 10no binaries found in the repo
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Pinned-Dependencies🟢 5dependency not pinned by hash detected -- score normalized to 5
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Branch-Protection🟢 8branch protection is not maximal on development and all release branches
Packaging🟢 10packaging workflow detected
SAST🟢 8SAST tool is not run on all commits -- score normalized to 8
npm/basic-ftp 5.2.1 🟢 5.1
Details
CheckScoreReason
Maintained🟢 55 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 5
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Security-Policy🟢 10security policy file detected
Packaging⚠️ -1packaging workflow not detected
Binary-Artifacts🟢 10no binaries found in the repo
Code-Review⚠️ 0Found 1/30 approved changesets -- score normalized to 0
Token-Permissions🟢 9detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies🟢 3dependency not pinned by hash detected -- score normalized to 3
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/lru-cache 11.3.3 🟢 5.8
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 13 issue activity found in the last 90 days -- score normalized to 10
Packaging⚠️ -1packaging workflow not detected
Code-Review⚠️ 0Found 0/25 approved changesets -- score normalized to 0
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 10security policy file detected
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
SAST🟢 10SAST tool is run on all commits
npm/netmask 2.1.1 UnknownUnknown
npm/react 19.2.5 🟢 6.6
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10
Code-Review🟢 9Found 25/26 approved changesets -- score normalized to 9
Security-Policy🟢 10security policy file detected
Packaging⚠️ -1packaging workflow not detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
CII-Best-Practices⚠️ 2badge detected: InProgress
Token-Permissions🟢 10GitHub workflow tokens follow principle of least privilege
License🟢 10license file detected
Binary-Artifacts🟢 9binaries present in source code
Pinned-Dependencies⚠️ 1dependency not pinned by hash detected -- score normalized to 1
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Fuzzing⚠️ 0project is not fuzzed
SAST⚠️ 2SAST tool is not run on all commits -- score normalized to 2
npm/react-dom 19.2.5 UnknownUnknown
npm/side-channel-list 1.0.1 UnknownUnknown

Scanned Files

  • web/package-lock.json

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a generic versioning system for several entities, including BudgetConfig, Company, and EvaluationConfig, by replacing the specific WorkflowDefinitionVersion model with a generic VersionSnapshot. It implements a unified SQLiteVersionRepository to handle these snapshots and adds corresponding API controllers for version history. My feedback suggests refactoring the nearly identical version controllers into a generic base class or factory to reduce code duplication and recommends adding a missing unit test for the Company model's serialization round-trip.

Comment on lines +28 to +78
class BudgetConfigVersionController(Controller):
"""Version history for budget configuration."""

path = "/budget/config"
tags = ("budget",)

@get("/versions", guards=[require_read_access])
async def list_versions(
self,
state: State,
offset: PaginationOffset = 0,
limit: PaginationLimit = 20,
) -> Response[PaginatedResponse[SnapshotT]]:
"""List version history for budget configuration."""
repo = state.app_state.persistence.budget_config_versions
versions = await repo.list_versions(
_ENTITY_ID,
limit=limit,
offset=offset,
)
total = await repo.count_versions(_ENTITY_ID)
meta = PaginationMeta(total=total, offset=offset, limit=limit)
return Response(
content=PaginatedResponse[SnapshotT](
data=versions,
pagination=meta,
),
)

@get(
"/versions/{version_num:int}",
guards=[require_read_access],
)
async def get_version(
self,
state: State,
version_num: Annotated[int, Parameter(ge=1)],
) -> Response[ApiResponse[SnapshotT]]:
"""Get a specific budget config version snapshot."""
repo = state.app_state.persistence.budget_config_versions
version = await repo.get_version(_ENTITY_ID, version_num)
if version is None:
return Response(
content=ApiResponse[SnapshotT](
error=f"Version {version_num} not found",
),
status_code=404,
)
return Response(
content=ApiResponse[SnapshotT](data=version),
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This controller is nearly identical to CompanyVersionController and EvaluationConfigVersionController. To improve maintainability and reduce code duplication, consider creating a generic base controller or a factory function to generate these simple, read-only version history controllers.

A factory could look something like this:

def create_version_controller(
    entity_name: str,
    path: str,
    tags: tuple[str, ...],
    repo_name: str,
    model_type: type,
    entity_id: str = "default",
) -> type[Controller]:
    SnapshotT = VersionSnapshot[model_type]

    class GenericVersionController(Controller):
        # ... implementation ...
    
    GenericVersionController.__name__ = f"{model_type.__name__}VersionController"
    return GenericVersionController

This would allow you to define each controller in just a few lines.

loaded = await repo.get_version("backend-developer", 1)
assert loaded is not None
assert loaded.snapshot.name == "backend-developer"
assert loaded.snapshot.department == "engineering"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test file validates round-trip serialization for most of the newly versioned entities, which is great. However, a test for the Company model seems to be missing.

Given that Company is also part of this versioning generalization, adding a test_company_roundtrip would ensure its serialization is also correctly handled by the generic SQLiteVersionRepository.

You could add a test like this:

# ── Company round-trip ──────────────────────────────────────────


@pytest.mark.unit
async def test_company_roundtrip() -> None:
    """Company survives serialization with defaults."""
    from synthorg.core.company import Company

    company = Company(name="Test Corp")

    async with aiosqlite.connect(":memory:") as db:
        repo = await _make_repo(
            db,
            "company_versions",
            Company,
        )
        snap = _make_snapshot("default", company)
        inserted = await repo.save_version(snap)
        assert inserted is True

        loaded = await repo.get_version("default", 1)
        assert loaded is not None
        assert loaded.snapshot.name == company.name

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR generalizes the workflow versioning implementation to the shared VersionSnapshot[T] / VersionRepository[T] infrastructure and expands the same version-history capability to additional entity types (BudgetConfig, EvaluationConfig, Company, Role), including new SQLite tables and API endpoints for config version history.

Changes:

  • Replace bespoke workflow version model/repo with generic VersionSnapshot[WorkflowDefinition] backed by SQLiteVersionRepository.
  • Add SQLite schema + persistence backend wiring for evaluation_config_versions, budget_config_versions, company_versions, and role_versions.
  • Add new API controllers + tests for BudgetConfig/Company/EvaluationConfig version history endpoints; update workflow version API/tests to the new snapshot shape.

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/persistence/test_protocol.py Updates PersistenceBackend conformance fake to include new version-repo properties.
tests/unit/persistence/sqlite/test_workflow_version_repo.py Removes tests for deleted bespoke SQLiteWorkflowVersionRepository.
tests/unit/persistence/sqlite/test_migrations.py Updates expected tables/indexes for new generic workflow version schema + new entity version tables.
tests/unit/persistence/sqlite/test_entity_version_repos.py Adds round-trip tests for generic SQLiteVersionRepository instantiations across entity types.
tests/unit/engine/workflow/test_version.py Removes tests for deleted WorkflowDefinitionVersion model.
tests/unit/engine/workflow/test_diff.py Migrates diff tests to wrap workflow definitions in VersionSnapshot.
tests/unit/api/fakes_workflow.py Updates fake workflow version repository to implement VersionRepository[WorkflowDefinition].
tests/unit/api/fakes_backend.py Adds fake persistence properties for the newly versioned entity repositories.
tests/unit/api/controllers/test_workflow_versions.py Updates assertions to the new { snapshot: ... } response shape.
tests/unit/api/controllers/test_config_version_controllers.py Adds tests for list/get version history endpoints for budget/company/evaluation configs.
src/synthorg/persistence/workflow_version_repo.py Deletes bespoke workflow version repo protocol.
src/synthorg/persistence/sqlite/workflow_version_repo.py Deletes bespoke SQLite workflow version repo implementation.
src/synthorg/persistence/sqlite/schema.sql Migrates workflow versions table to generic snapshot format + adds new version tables and indexes.
src/synthorg/persistence/sqlite/backend.py Wires SQLiteVersionRepository[T] instances for workflow + 4 additional entity types.
src/synthorg/persistence/protocol.py Extends PersistenceBackend protocol with new version repository properties and generic workflow type.
src/synthorg/engine/workflow/version.py Deletes bespoke workflow version snapshot model.
src/synthorg/engine/workflow/diff.py Updates compute_diff() to accept VersionSnapshot[WorkflowDefinition] and unwrap at the top level.
src/synthorg/api/state.py Injects budget_config_versions repo into OrgMutationService.
src/synthorg/api/services/org_mutations.py Adds best-effort budget config snapshotting when budget-related company fields change.
src/synthorg/api/controllers/workflows.py Switches workflow snapshot writes to VersioningService.snapshot_if_changed() and updates delete call.
src/synthorg/api/controllers/workflow_versions.py Migrates workflow version APIs to VersionSnapshot responses + uses VersioningService for rollback snapshotting.
src/synthorg/api/controllers/evaluation_config_versions.py New controller providing list/get for evaluation config versions.
src/synthorg/api/controllers/company_versions.py New controller providing list/get for company versions.
src/synthorg/api/controllers/budget_config_versions.py New controller providing list/get for budget config versions.
src/synthorg/api/controllers/_workflow_helpers.py Removes obsolete build_version_snapshot() helper.
src/synthorg/api/controllers/init.py Registers the 3 new config version controllers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +6
"""Tests for entity-specific version repository instantiations.

Validates that WorkflowDefinition, BudgetConfig, EvaluationConfig,
Company, and Role models survive JSON round-trip serialization through
the generic SQLiteVersionRepository.
"""
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module docstring claims this test suite validates JSON round-trip for Company, but there is currently no company round-trip test in this file. Either add a Company round-trip test or update the docstring to match what’s actually covered, so the test intent stays accurate.

Copilot uses AI. Check for mistakes.
Comment on lines +187 to +211
@pytest.mark.unit
async def test_content_hash_dedup() -> None:
"""Saving the same content twice returns False on second insert."""
from synthorg.budget.config import BudgetConfig

config = BudgetConfig()

async with aiosqlite.connect(":memory:") as db:
repo = await _make_repo(
db,
"budget_versions",
BudgetConfig,
)
snap1 = _make_snapshot("default", config, version=1)
assert await repo.save_version(snap1) is True

# Same content, different version number.
snap2 = _make_snapshot("default", config, version=2)
assert await repo.save_version(snap2) is True

# Same version number (duplicate PK) -- rejected.
assert await repo.save_version(snap1) is False

count = await repo.count_versions("default")
assert count == 2
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_content_hash_dedup doesn’t actually test content-hash deduplication: it inserts the same content under different version numbers and expects both inserts to succeed. If the goal is to test “skip no-change saves”, this should exercise VersioningService.snapshot_if_changed() (or otherwise assert a save is skipped when the latest content hash matches). Otherwise, rename the test/docstring to reflect that it’s only testing PK idempotency / duplicate version handling.

Copilot uses AI. Check for mistakes.
Comment on lines 439 to 449
CREATE TABLE IF NOT EXISTS workflow_definition_versions (
definition_id TEXT NOT NULL CHECK(length(definition_id) > 0),
entity_id TEXT NOT NULL CHECK(length(entity_id) > 0),
version INTEGER NOT NULL CHECK(version >= 1),
name TEXT NOT NULL CHECK(length(name) > 0),
description TEXT NOT NULL DEFAULT '',
workflow_type TEXT NOT NULL CHECK(workflow_type IN (
'sequential_pipeline', 'parallel_execution', 'kanban', 'agile_kanban'
)),
nodes TEXT NOT NULL,
edges TEXT NOT NULL,
created_by TEXT NOT NULL CHECK(length(created_by) > 0),
content_hash TEXT NOT NULL CHECK(length(content_hash) > 0),
snapshot TEXT NOT NULL CHECK(length(snapshot) > 0),
saved_by TEXT NOT NULL CHECK(length(saved_by) > 0),
saved_at TEXT NOT NULL,
PRIMARY KEY (definition_id, version),
FOREIGN KEY (definition_id)
REFERENCES workflow_definitions(id)
ON DELETE CASCADE
saved_at TEXT NOT NULL CHECK(
saved_at LIKE '%+00:00' OR saved_at LIKE '%Z'
),
PRIMARY KEY (entity_id, version)
);
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

workflow_definition_versions no longer has a foreign key back to workflow_definitions(id). That means deleting a workflow definition outside the explicit controller path can leave orphaned version rows, and DB-level referential integrity is lost. Consider restoring an FOREIGN KEY (entity_id) REFERENCES workflow_definitions(id) ON DELETE CASCADE constraint for this table (even if other version tables remain FK-less).

Copilot uses AI. Check for mistakes.
Comment on lines +283 to +285
new_etag = await self._company_snapshot_etag()
if "budget_monthly" in updated:
await self._snapshot_budget_config(saved_by="api")
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Budget config version snapshots are being attributed to saved_by="api", even though this mutation is triggered by an authenticated HTTP request. If these snapshots are meant to provide an audit trail, consider threading the caller identity into OrgMutationService.update_company() (e.g., pass saved_by=get_auth_user_id(request) from the controller) so the version history records the real actor.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/synthorg/api/controllers/workflow_versions.py (1)

364-376: ⚠️ Potential issue | 🟡 Minor

Snapshot failure is logged but not communicated to the client.

When the version snapshot fails after a successful rollback, the error is logged but the client receives a 200 OK response without any indication that the snapshot wasn't persisted. This could lead to confusion when the version history doesn't reflect the rollback.

Consider either:

  1. Including a warning in the response payload indicating partial success
  2. Documenting this behavior clearly in the API contract

The current approach handles the error explicitly (logging), so it's not a silent swallow, but the client has no visibility into this partial failure state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/controllers/workflow_versions.py` around lines 364 - 376,
The rollback currently calls VersioningService.snapshot_if_changed and catches
PersistenceError (svc, snapshot_if_changed, PersistenceError) but only logs via
logger.exception(WORKFLOW_VERSION_SNAPSHOT_FAILED) so the client still gets a
200 with no indication of snapshot failure; update the controller handling
around svc.snapshot_if_changed (and the rolled_back/updater flow) to surface the
partial-failure to the client by adding a warning flag/message in the response
payload (e.g., include snapshot_status: "failed" or warning: "snapshot
persistence failed") or alternatively return a 207/appropriate error code, and
ensure the logged message is preserved; adjust response construction to include
the new warning field when PersistenceError is caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/api/controllers/budget_config_versions.py`:
- Around line 34-78: Add DEBUG logging in list_versions and WARNING logging in
get_version: define new observability event constants (e.g.,
BUDGET_CONFIG_VERSION_LISTED and BUDGET_CONFIG_VERSION_NOT_FOUND) in
synthorg.observability.events, then in budget_config_versions.list_versions log
a debug event with context (entity id _ENTITY_ID, offset, limit, total) before
returning, and in get_version log a warning with context (entity id _ENTITY_ID,
version_num) when version is None before returning the 404 response; use the
same logging facility used elsewhere (match style in workflow_versions) and
attach the event constants to the log calls.

In `@src/synthorg/persistence/sqlite/backend.py`:
- Around line 276-331: The repeated serialize_snapshot/deserialize_snapshot
lambdas across SQLiteVersionRepository instantiations (e.g., _workflow_versions,
_identity_versions, _evaluation_config_versions, _budget_config_versions,
_company_versions, _role_versions) should be extracted into a small
factory/helper to avoid duplication; add a generic helper (e.g.,
_json_serializer and _make_version_repo) that accepts the db, table_name and
model class and returns a SQLiteVersionRepository with serialize_snapshot using
model.model_dump(mode="json") and deserialize_snapshot using
model_cls.model_validate(json.loads(s)), then replace each direct
SQLiteVersionRepository(...) call with a call to that helper.

In `@src/synthorg/persistence/sqlite/schema.sql`:
- Around line 437-454: The schema change for table workflow_definition_versions
renames definition_id→entity_id, drops several columns and adds
content_hash/snapshot which breaks existing DBs because migrations.py only
supports _add_column_if_missing for a small allowlist; fix by adding an explicit
migration in src/synthorg/persistence/sqlite/migrations.py that handles the
workflow_definition_versions transformation: detect legacy schema (presence of
definition_id and old columns), create a new temp table with the new schema
(entity_id, version, content_hash, snapshot, saved_by, saved_at), copy/transform
data from old columns into the temp table (map definition_id→entity_id,
compute/NULL new columns), drop the old table and rename the temp table to
workflow_definition_versions, and add this migration to the migration sequence;
alternatively, if opting not to auto-migrate, add clear upgrade documentation
and a manual SQL migration script mentioning the exact column changes
(definition_id, name, description, workflow_type, nodes, edges, created_by →
removed; content_hash, snapshot → added) and ensure _add_column_if_missing
remains unchanged.

In `@tests/unit/api/controllers/test_config_version_controllers.py`:
- Around line 133-176: Add tests mirroring TestBudgetConfigVersions: implement
test_list_versions_with_data and test_list_versions_paginated for company
snapshots so Company endpoints have equivalent coverage. Use
fake_persistence.company_versions (as in test_get_version) to save multiple
snapshots via _snap("name", company), then call
test_client.get("/api/v1/company/versions", headers=make_auth_headers("ceo")) to
assert the returned list contains the saved versions and that pagination
parameters (limit/offset or page) behave as expected; name the tests
test_list_versions_with_data and test_list_versions_paginated to match existing
patterns.
- Around line 182-224: Add a new test method named test_list_versions_with_data
to the TestEvaluationConfigVersions class that persists multiple
EvaluationConfig snapshots via
fake_persistence.evaluation_config_versions.save_version (use _snap to create
versions), then calls the list endpoint "/api/v1/evaluation/config/versions"
with make_auth_headers("ceo") and asserts status_code 200 and that the returned
data contains the saved snapshots in the expected order and with correct version
numbers; mirror the structure of Company’s list_versions_with_data test to
verify ordering and contents.

In `@tests/unit/persistence/sqlite/test_entity_version_repos.py`:
- Around line 1-6: Docstring mentions Company but no test exists; add a unit
test named test_company_roundtrip that mirrors the existing round-trip tests:
import Company from synthorg.core.company, construct Company(name="Test Corp"),
create an in-memory aiosqlite DB, call _make_repo(db, "company_versions",
Company), build a snapshot with _make_snapshot("default", company), call
repo.save_version(snap) and assert True, then repo.get_version("default", 1) and
assert the loaded snapshot.name == "Test Corp"; alternatively, if you prefer not
to add a test, update the module docstring to remove "Company" from the
validated models list.

---

Outside diff comments:
In `@src/synthorg/api/controllers/workflow_versions.py`:
- Around line 364-376: The rollback currently calls
VersioningService.snapshot_if_changed and catches PersistenceError (svc,
snapshot_if_changed, PersistenceError) but only logs via
logger.exception(WORKFLOW_VERSION_SNAPSHOT_FAILED) so the client still gets a
200 with no indication of snapshot failure; update the controller handling
around svc.snapshot_if_changed (and the rolled_back/updater flow) to surface the
partial-failure to the client by adding a warning flag/message in the response
payload (e.g., include snapshot_status: "failed" or warning: "snapshot
persistence failed") or alternatively return a 207/appropriate error code, and
ensure the logged message is preserved; adjust response construction to include
the new warning field when PersistenceError is caught.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a423bb04-964b-42d4-80d2-bed204d49cd6

📥 Commits

Reviewing files that changed from the base of the PR and between 63a9390 and 31be88d.

📒 Files selected for processing (26)
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/_workflow_helpers.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/state.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/engine/workflow/version.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/persistence/sqlite/schema.sql
  • src/synthorg/persistence/sqlite/workflow_version_repo.py
  • src/synthorg/persistence/workflow_version_repo.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/api/fakes_backend.py
  • tests/unit/api/fakes_workflow.py
  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/engine/workflow/test_version.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/persistence/sqlite/test_workflow_version_repo.py
  • tests/unit/persistence/test_protocol.py
💤 Files with no reviewable changes (6)
  • tests/unit/engine/workflow/test_version.py
  • src/synthorg/engine/workflow/version.py
  • src/synthorg/persistence/workflow_version_repo.py
  • src/synthorg/persistence/sqlite/workflow_version_repo.py
  • tests/unit/persistence/sqlite/test_workflow_version_repo.py
  • src/synthorg/api/controllers/_workflow_helpers.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Deploy Preview
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Dependency Review
  • GitHub Check: Agent
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (go)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: (no parentheses) -- PEP 758 except syntax, enforced by ruff on Python 3.14
All public functions must have type hints. Use mypy strict mode.
Google style docstrings are required on public classes and functions. Enforced by ruff D rules.
Use immutability patterns: create new objects, never mutate existing ones. For non-Pydantic internal collections, use copy.deepcopy() at construction + MappingProxyType wrapping.
Use frozen Pydantic models for config/identity; separate mutable-via-copy models for runtime state. Never mix static config fields with mutable runtime fields.
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Include allow_inf_nan=False in all ConfigDict declarations.
Use @computed_field for derived values instead of storing + validating redundant fields
Use NotBlankStr (from core.types) for all identifier/name fields, including optional and tuple variants, instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code
Line length must be 88 characters. Enforced by ruff.
Functions must be less than 50 lines, files must be less than 800 lines
Handle errors explicitly, never silently swallow exceptions
Validate at system boundaries (user input, external APIs, config files)
Always use variable name logger (not _logger, not log) for logger instances

Files:

  • tests/unit/api/fakes_backend.py
  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/workflows.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/persistence/test_protocol.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/persistence/protocol.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • tests/unit/api/fakes_workflow.py
  • src/synthorg/api/controllers/workflow_versions.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Use asyncio_mode = 'auto' for async tests -- no manual @pytest.mark.asyncio needed
Global test timeout is 30 seconds per test in pyproject.toml. Non-default overrides like timeout(60) are allowed, but do not add per-file pytest.mark.timeout(30) markers.
Prefer @pytest.mark.parametrize for testing similar cases
Use Hypothesis for property-based testing with @given + @settings decorators
Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep(). For indefinite blocks, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/api/fakes_backend.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/persistence/test_protocol.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • tests/unit/api/fakes_workflow.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/api/fakes_backend.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/persistence/test_protocol.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • tests/unit/api/fakes_workflow.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST import from synthorg.observability import get_logger and set logger = get_logger(__name__)
Never use import logging or logging.getLogger() or print() in application code. Exceptions: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, observability/http_handler.py, observability/otlp_handler.py may use stdlib logging and stderr print for handler construction.
Event names must always use constants from domain-specific modules under synthorg.observability.events. Import directly from specific domain modules.
Use structured kwargs for logging: always logger.info(EVENT, key=value) -- never logger.info('msg %s', val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO level
DEBUG logging should be used for object creation, internal flow, and entry/exit of key functions
RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig

Files:

  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/workflow_versions.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Never use real vendor names (Anthropic, OpenAI, Claude, GPT) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, test-provider, test-small-001, etc.

Files:

  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/workflow_versions.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/workflow_versions.py
🧠 Learnings (19)
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/api/state.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/api/state.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/api/**/*.py : REST API: Litestar framework, controllers with guards, channels for WebSocket, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint. RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation).

Applied to files:

  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/workflow_versions.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers

Applied to files:

  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/workflow_versions.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to {pyproject.toml,src/synthorg/__init__.py} : Update version in `pyproject.toml` (`[tool.commitizen].version`) and `src/synthorg/__init__.py` (`__version__`)

Applied to files:

  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/workflow_versions.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to src/**/*.py : Use event name constants from domain-specific modules under ai_company.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.) — import directly

Applied to files:

  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/synthorg/api/**/*.py : Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Applied to files:

  • src/synthorg/api/controllers/workflow_versions.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/api/**/*.py : Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.

Applied to files:

  • src/synthorg/api/controllers/workflow_versions.py
🔇 Additional comments (24)
src/synthorg/engine/workflow/diff.py (1)

167-206: LGTM!

The migration to VersionSnapshot[WorkflowDefinition] is well-implemented. The function correctly:

  • Compares entity_id for cross-definition validation
  • Extracts the snapshot attribute for actual diff computation
  • Sets definition_id from old.entity_id
tests/unit/api/fakes_backend.py (1)

236-239: LGTM!

The new version repositories are correctly added following the existing pattern. The FakeVersionRepository class is appropriately generic to handle all snapshot types.

Also applies to: 375-390

src/synthorg/api/state.py (1)

200-212: LGTM!

The budget_config_versions parameter is correctly wired to OrgMutationService in both initialization paths with proper null-safety handling.

Also applies to: 842-850

src/synthorg/api/controllers/workflows.py (2)

421-435: LGTM!

The migration to VersioningService.snapshot_if_changed() is correctly implemented, replacing the manual build_version_snapshot() + save_version() pattern. The broader PersistenceError catch aligns with the PR objective.


614-617: LGTM!

The method name is correctly updated from delete_versions_for_definition to delete_versions_for_entity, matching the VersionRepository protocol.

tests/unit/persistence/sqlite/test_migrations.py (1)

45-48: LGTM!

The expected tables and indexes are correctly updated to match the new schema with:

  • Four new version tables for evaluation_config, budget_config, company, and role
  • Renamed workflow version indexes (idx_wdv_entity_saved, idx_wdv_content_hash)
  • Consistent index naming pattern across all version tables

Also applies to: 108-109, 124-131

tests/unit/api/controllers/test_workflow_versions.py (1)

146-147: LGTM!

The test assertions are correctly updated to access the name field through the nested snapshot object, reflecting the new VersionSnapshot[WorkflowDefinition] response structure.

Also applies to: 194-194, 284-284

tests/unit/engine/workflow/test_diff.py (1)

56-89: LGTM!

The _ver helper is well-refactored to build VersionSnapshot[WorkflowDefinition] objects. The backward-compatible handling of definition_id override (mapped to entity_id) and the proper synchronization between entity_id and WorkflowDefinition.id ensures existing tests continue to work correctly.

src/synthorg/api/controllers/__init__.py (1)

15-17: LGTM!

The three new version controllers are correctly imported, registered in ALL_CONTROLLERS, and exported via __all__. Per the app wiring (context snippets), adding to ALL_CONTROLLERS is sufficient for automatic route registration.

Also applies to: 23-25, 31-33, 98-100, 117-117, 122-122, 127-127

src/synthorg/api/services/org_mutations.py (2)

88-113: Versioning helper correctly implements best-effort semantics.

The _snapshot_budget_config method appropriately:

  1. Returns early when versioning is disabled (line 99-100)
  2. Uses broad exception handling to avoid blocking mutations (line 108)
  3. Logs failures with structured context (lines 109-113)

The placement inside _org_lock (lines 284-285) correctly addresses the race condition mentioned in the PR objectives.


77-86: Optional dependency injection pattern is well-implemented.

The use of VersionRepository[BudgetConfig] | None with conditional VersioningService initialization follows the project's optional-feature pattern cleanly.

tests/unit/persistence/test_protocol.py (1)

555-591: Protocol conformance stubs follow established pattern.

The new version repository properties (evaluation_config_versions, budget_config_versions, company_versions, role_versions) correctly mirror the existing stub pattern for @runtime_checkable protocol compliance testing.

src/synthorg/persistence/protocol.py (1)

263-295: Generic version repository protocol properties are well-defined.

The new evaluation_config_versions, budget_config_versions, company_versions, and role_versions properties follow the established pattern from identity_versions and provide proper type hints with VersionRepository[T].

src/synthorg/api/controllers/evaluation_config_versions.py (2)

34-78: Controller implementation follows established patterns.

The endpoint implementations are correct:

  • Proper pagination with PaginationMeta
  • Appropriate 404 handling for missing versions
  • Parameter(ge=1) constraint on version numbers
  • Read access guard applied consistently

17-20: 🧹 Nitpick | 🔵 Trivial

Logger imported but unused.

The logger is initialized but never used in this controller. Consider adding DEBUG logging for endpoint entry/exit or INFO for version lookups, or remove the unused import if no logging is needed.

⛔ Skipped due to learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Variable name for logger: always `logger` (not `_logger`, not `log`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import logger via `from synthorg.observability import get_logger` and initialize with `logger = get_logger(__name__)`
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:24:49.467Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST import `from synthorg.observability import get_logger` and set `logger = get_logger(__name__)`
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have `from synthorg.observability import get_logger` followed by `logger = get_logger(__name__)`.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`
tests/unit/api/controllers/test_config_version_controllers.py (1)

23-35: Generic helper function is well-designed.

The _snap[T: BaseModel] helper using PEP 695 syntax provides clean test data creation with proper content hash computation.

tests/unit/persistence/sqlite/test_entity_version_repos.py (1)

36-67: Generic test helpers are well-implemented.

The _make_snapshot and _make_repo helpers provide clean abstractions for creating test fixtures with proper type parameterization.

src/synthorg/api/controllers/company_versions.py (2)

28-78: Controller implementation is correct and consistent.

The CompanyVersionController correctly implements the version history endpoints with proper pagination, 404 handling, and read access guards.


17-20: 🧹 Nitpick | 🔵 Trivial

Logger imported but unused.

Same as evaluation_config_versions.py - the logger is initialized but never used. Consider adding logging or removing the unused import.

⛔ Skipped due to learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Variable name for logger: always `logger` (not `_logger`, not `log`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import logger via `from synthorg.observability import get_logger` and initialize with `logger = get_logger(__name__)`
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:24:49.467Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST import `from synthorg.observability import get_logger` and set `logger = get_logger(__name__)`
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have `from synthorg.observability import get_logger` followed by `logger = get_logger(__name__)`.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Never use `import logging`, `logging.getLogger()`, or `print()` in application code; use the injected logger instead
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Never use `import logging`, `logging.getLogger()`, or `print()` in application code — use the synthorg logger instead
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Never use `import logging` / `logging.getLogger()` / `print()` in application code
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T12:36:58.294Z
Learning: Applies to src/synthorg/**/*.py : Never use `import logging` / `logging.getLogger()` / `print()` in application code; exceptions: `observability/setup.py` and `observability/sinks.py` may use stdlib logging and `print(..., file=sys.stderr)` for bootstrap code
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to src/**/*.py : Never use `import logging` / `logging.getLogger()` / `print()` in application code (exception: `observability/setup.py` and `observability/sinks.py` may use stdlib `logging` and `print(..., file=sys.stderr)` for bootstrap)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:24:49.467Z
Learning: Applies to src/synthorg/**/*.py : Never use `import logging` or `logging.getLogger()` or `print()` in application code. Exceptions: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, observability/http_handler.py, observability/otlp_handler.py may use stdlib logging and stderr print for handler construction.
src/synthorg/persistence/sqlite/backend.py (2)

667-721: New version repository properties follow established pattern.

The property accessors for evaluation_config_versions, budget_config_versions, company_versions, and role_versions correctly use _require_connected and include proper docstrings with Raises sections.


143-154: Instance variable declarations are properly typed.

The new nullable repository references use correct generic type annotations (e.g., SQLiteVersionRepository[BudgetConfig] | None).

src/synthorg/api/controllers/workflow_versions.py (1)

181-189: LGTM!

The rollback construction correctly restores content from the target snapshot while preserving identity fields (id, created_by, created_at) from the existing definition and properly bumping the version number. This aligns well with the migration to VersionSnapshot[T].

tests/unit/api/fakes_workflow.py (1)

112-186: LGTM!

The FakeWorkflowVersionRepository correctly implements the VersionRepository[WorkflowDefinition] protocol with proper semantics:

  • save_version returns bool indicating insert vs. duplicate (consistent with FakeVersionRepository in fakes_backend.py)
  • New methods get_latest_version and get_by_content_hash support conflict resolution in VersioningService
  • Method renamed from delete_versions_for_definition to delete_versions_for_entity matching the protocol
  • Immutability maintained via copy.deepcopy() on all returns
src/synthorg/persistence/sqlite/schema.sql (1)

597-671: LGTM on new version tables.

The four new versioning tables (evaluation_config_versions, budget_config_versions, company_versions, role_versions) follow a consistent pattern with:

  • Proper IF NOT EXISTS guards for idempotent creation
  • Consistent column structure matching the generic VersionSnapshot[T] model
  • Appropriate indexes for entity listing and content-hash lookups
  • UTC timestamp validation via CHECK constraints

This design enables the generic SQLiteVersionRepository[T] to work uniformly across all entity types.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 8, 2026

Codecov Report

❌ Patch coverage is 87.41007% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.89%. Comparing base (581c494) to head (f410cff).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/api/services/org_mutations.py 65.62% 10 Missing and 1 partial ⚠️
src/synthorg/persistence/sqlite/migrations.py 33.33% 8 Missing and 2 partials ⚠️
src/synthorg/api/controllers/workflows.py 73.68% 5 Missing ⚠️
src/synthorg/engine/workflow/diff.py 60.00% 3 Missing and 1 partial ⚠️
src/synthorg/persistence/sqlite/backend.py 89.18% 4 Missing ⚠️
src/synthorg/api/controllers/workflow_versions.py 90.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main    #1155    +/-   ##
========================================
  Coverage   88.88%   88.89%            
========================================
  Files         857      858     +1     
  Lines       50440    50574   +134     
  Branches     5072     5081     +9     
========================================
+ Hits        44835    44958   +123     
- Misses       4643     4650     +7     
- Partials      962      966     +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/engine.md`:
- Around line 230-231: The design doc's schema description for workflow
versioning is incomplete: update the paragraph referencing
VersionSnapshot[WorkflowDefinition] and VersioningService[WorkflowDefinition] to
reflect the actual generic version table schema used (e.g.,
workflow_definition_versions) by adding the missing saved_by and saved_at
columns alongside entity_id, version, content_hash, and snapshot; preserve the
note about content-hash deduplication and INSERT OR IGNORE conflict retry but
explicitly document saved_by and saved_at semantics (who and when) to eliminate
the design/implementation drift.

In `@src/synthorg/api/services/org_mutations.py`:
- Around line 91-114: The _snapshot_budget_config method currently only catches
PersistenceError but calls self._resolver.get_budget_config which can raise
SettingNotFoundError or other resolver exceptions; update the try/except to
either catch SettingNotFoundError in addition to PersistenceError or broaden to
a general Exception to preserve the "best-effort" behavior, and when logging
VERSION_SNAPSHOT_FAILED include the caught exception info (keep existing
entity_type/entity_id context) so resolver failures don't crash the mutation
unless you intend them to—adjust the docstring if you choose to let resolver
errors propagate.

In `@src/synthorg/engine/workflow/diff.py`:
- Around line 186-203: Add guard checks that the snapshots themselves belong to
the same entity before computing diffs: after confirming old.entity_id ==
new.entity_id, verify old.snapshot.id == new.snapshot.id (and/or any
snapshot-specific identifier you use) and log a warning via
WORKFLOW_DEF_INVALID_REQUEST with old_snapshot_id and new_snapshot_id if they
differ, then raise ValueError to abort (similar to the existing entity_id
check). Update the block containing old.entity_id/new.entity_id, the uses of
old.snapshot and new.snapshot, and ensure WorkflowDiff construction remains
unchanged when snapshots pass the check.

In `@src/synthorg/persistence/sqlite/schema.sql`:
- Around line 437-461: The comment in schema.sql references a non-existent
migration doc; either add the missing migration file
docs/migrations/workflow-version-schema.md with clear manual migration steps for
the breaking change that converts the old flattened workflow fields to the
VersionSnapshot format (affecting workflow_definition_versions table/entity_id,
version, content_hash, snapshot, saved_by, saved_at), or remove/update the
inline comment in src/synthorg/persistence/sqlite/schema.sql (the block above
CREATE TABLE workflow_definition_versions and the note about FK removal) to no
longer point to the missing file and instead provide a concise alternative
pointer (e.g., to an existing migration guide or an inline brief migration
note).

In `@tests/unit/api/controllers/test_config_version_controllers.py`:
- Around line 1-5: The module docstring currently lists
BudgetConfigVersionController, CompanyVersionController, and
EvaluationConfigVersionController but omits RoleVersionController; update the
top-of-file docstring in
tests/unit/api/controllers/test_config_version_controllers.py to include
RoleVersionController so it accurately reflects all controllers covered by the
tests (mention RoleVersionController alongside the existing three controller
names).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 22f7994c-9b22-482a-b706-6213f451fc9c

📥 Commits

Reviewing files that changed from the base of the PR and between 31be88d and b7018c8.

📒 Files selected for processing (18)
  • CLAUDE.md
  • docs/design/engine.md
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/observability/events/versioning.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/persistence/sqlite/schema.sql
  • src/synthorg/persistence/sqlite/version_repo.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • web/src/api/types.ts
  • web/src/pages/workflow-editor/VersionHistoryPanel.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Dashboard Test
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (8)
web/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (web/CLAUDE.md)

web/src/**/*.{ts,tsx,js,jsx}: Always use createLogger from @/lib/logger -- never bare console.warn/console.error/console.debug in application code
Logger variable name must always be const log (e.g. const log = createLogger('module-name'))
Pass dynamic/untrusted values as separate arguments to logger methods (not interpolated into the message string) so they go through sanitizeArg
Attacker-controlled fields inside structured objects must be wrapped in sanitizeForLog() before embedding in log calls

Files:

  • web/src/pages/workflow-editor/VersionHistoryPanel.tsx
  • web/src/api/types.ts
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (web/CLAUDE.md)

web/src/**/*.{ts,tsx}: Use Tailwind semantic classes (text-foreground, bg-card, text-accent, text-success, bg-danger, etc.) or CSS variables (var(--so-*)) for colors; NEVER hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (Geist tokens) for typography; NEVER set fontFamily directly in .tsx/.ts files
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; NEVER hardcode pixel values for layout spacing in components
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows and borders; NEVER hardcode values in .tsx/.ts files
Use @/lib/motion presets for Framer Motion transition durations; NEVER hardcode transition durations
CSS side-effect imports in TypeScript 6 require type declarations -- add /// <reference types="vite/client" /> at the top of files with CSS imports

ALWAYS reuse existing components from web/src/components/ui/ before creating new ones

NEVER hardcode hex colors, font-family, pixel spacing, or Framer Motion transitions — use design tokens and @/lib/motion presets

Files:

  • web/src/pages/workflow-editor/VersionHistoryPanel.tsx
  • web/src/api/types.ts
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

No from __future__ import annotations — Python 3.14 has PEP 649 native lazy annotations

Use PEP 758 except syntax: except A, B: (no parentheses) — ruff enforces this on Python 3.14

All public functions and classes must have type hints. Use mypy strict mode

Use Google style docstrings, required on all public classes and functions — enforced by ruff D rules

Create new objects for immutability, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement

For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)

Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model

Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). In all ConfigDict declarations, use allow_inf_nan=False to reject NaN/Inf in numeric fields at validation time

Use @computed_field for derived values instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens)

Use NotBlankStr (from core.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants — instead of manual whitespace validators

Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task

Keep line length to 88 characters — ruff enforces this

Functions must be < 50 lines, files < 800 lines

Handle errors explicitly, never silently swallow them

Validate at system boundaries (user input, external APIs, c...

Files:

  • src/synthorg/observability/events/versioning.py
  • src/synthorg/api/controllers/company.py
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/company_versions.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/engine/workflow/diff.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/persistence/sqlite/backend.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)

Logging variable name must always be logger (not _logger, not log)

Use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool, etc.). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT

Always use structured kwargs in logging: logger.info(EVENT, key=value) — never logger.info("msg %s", val)

All error paths must log at WARNING or ERROR with context before raising

All state transitions must log at INFO

Use DEBUG logging for object creation, internal flow, and entry/exit of key functions

All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code

RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig

Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately without retry. RetryExhaustedError signals all retries failed — the engine layer catches this to trigger fallback chains

Rate limiter respects RateLimitError.retry_after from providers — automatically pauses future requests

Vendor-agnostic everywhere: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small aliases

Files:

  • src/synthorg/observability/events/versioning.py
  • src/synthorg/api/controllers/company.py
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/persistence/sqlite/backend.py
src/**/*.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/observability/events/versioning.py
  • src/synthorg/api/controllers/company.py
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/persistence/sqlite/backend.py
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Read the relevant docs/design/ page before implementing any feature or planning any issue. The design spec is the starting point for architecture, data models, and behavior

When approved deviations occur, update the relevant docs/design/ page to reflect the new reality

Files:

  • docs/design/engine.md
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Vendor names may appear in: (1) Operations design page provider list (docs/design/operations.md), (2) third-party import paths/module names in docstrings (e.g. litellm.types.llms.openai), (3) provider presets (src/synthorg/providers/presets.py) which are user-facing runtime data

Files:

  • docs/design/engine.md
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)

Logging variable name must always be logger (not _logger, not log)

Use markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow

80% coverage minimum — enforced in CI

Use asyncio_mode = "auto" for async tests — no manual @pytest.mark.asyncio needed

30 second timeout per test (global in pyproject.toml) — do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed

Use parallelism with pytest-xdist via -n 8 — ALWAYS include -n 8 when running pytest locally, never run tests sequentially

Prefer @pytest.mark.parametrize for testing similar cases

Vendor-agnostic everywhere: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small aliases. Use test-provider, test-small-001, etc. in tests

Use Hypothesis for property-based testing with @given + @settings. Hypothesis profiles: ci (deterministic, 10 examples + derandomize=True), dev (1000 examples), fuzz (10,000 examples, no deadline), extreme (500,000 examples, no deadline). Control via HYPOTHESIS_PROFILE env var

When Hypothesis finds a failure, it is a real bug — fix the underlying bug and add an explicit @example(...) decorator to the test so the case is permanently covered in CI. Do NOT just rerun and move on

NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number)

Files:

  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/api/controllers/test_config_version_controllers.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/api/controllers/test_config_version_controllers.py
🧠 Learnings (27)
📓 Common learnings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: If implementation deviates from the spec (better approach found, scope evolved, etc.), alert the user and explain why — user decides whether to proceed or update the spec. Do NOT silently diverge
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: When a spec topic is referenced, read the relevant `docs/design/` page before coding
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding starts
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: At every phase of planning and implementation, be critical and actively look for ways to improve the design in the spirit of robustness, correctness, simplicity, and future-proofing. Surface improvements as suggestions, not silent changes
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Prioritize issues by dependency order, not priority labels — unblocked dependencies come first
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Never use `import logging` / `logging.getLogger()` / `print()` in application code. Exception: `observability/setup.py`, `observability/sinks.py`, `observability/syslog_handler.py`, `observability/http_handler.py`, and `observability/otlp_handler.py` may use stdlib `logging` and `print(..., file=sys.stderr)` for handler construction, bootstrap, and error reporting code
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Pure data models, enums, and re-exports do NOT need logging
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: See `web/CLAUDE.md` for the full component inventory, design token rules, and post-training references. A PostToolUse hook (`scripts/check_web_design_system.py`) enforces design system rules on every Edit/Write to `web/src/`
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: NEVER use `cd` in Bash commands — the working directory is already set to the project root. Use absolute paths or run commands directly
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Exception to no-cd rule: `bash -c "cd <dir> && <cmd>"` is safe (runs in a child process, no cwd side effects). Use this for tools without a `-C` flag
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: NEVER use Bash to write or modify files — use the Write or Edit tools. Do not use `cat >`, `cat << EOF`, `echo >`, `echo >>`, `sed -i`, `python -c "open(...).write(...)", or `tee` to create or modify files
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Commits must use format: `<type>: <description>` with types: feat, fix, refactor, docs, test, chore, perf, ci. Enforced by commitizen (commit-msg hook)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Signed commits are required on `main` via branch protection — all commits must be GPG/SSH signed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Use branch naming: `<type>/<slug>` from main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint + go vet, no-em-dashes, no-redundant-timeout, eslint-web
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Hookify rules (committed in `.claude/hookify.*.md`): block-pr-create (blocks direct `gh pr create`), enforce-parallel-tests (enforces `-n 8` with pytest), no-cd-prefix (blocks `cd` prefix in Bash commands), no-local-coverage (blocks `--cov` locally)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Pre-push hooks: mypy type-check (affected modules only) + pytest unit tests (affected modules only) + golangci-lint + go vet + go test + eslint-web. Foundational module changes (core, config, observability) or conftest changes trigger full runs
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Use `gh issue list` via Bash (not MCP tools) for GitHub issue queries — MCP `list_issues` has unreliable field data
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Merge strategy: squash merge — PR body becomes the squash commit message on main. Trailers (e.g. `Release-As`, `Closes `#N``) must be in the PR body to land in the final commit
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Preserve existing `Closes `#NNN`` references in PR issue references — never remove unless explicitly asked
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: After finishing an issue implementation: create a feature branch (`<type>/<slug>`), commit, and push — do NOT create a PR automatically. Do NOT leave work uncommitted on main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: NEVER create a PR directly — `gh pr create` is blocked by hookify. ALWAYS use `/pre-pr-review` to create PRs — it runs automated checks + review agents + fixes before creating the PR
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: For trivial/docs-only changes: `/pre-pr-review quick` skips agents but still runs automated checks
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: After the PR exists, use `/aurelio-review-pr` to handle external reviewer feedback
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:29:49.081Z
Learning: Fix everything valid when review agents find issues — including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes. No deferring, no "out of scope" skipping
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers

Applied to files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/role_versions.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/api/**/*.py : REST API: Litestar framework, controllers with guards, channels for WebSocket, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint. RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation).

Applied to files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/company_versions.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-04-06T16:35:12.934Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-06T16:35:12.934Z
Learning: All project conventions, commands, and standards are defined in CLAUDE.md - refer to it for project structure, package layout, code conventions, quick commands, git workflow, testing standards, design specifications, logging, resilience, and security patterns

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-04-02T12:07:44.443Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T12:07:44.443Z
Learning: Applies to src/synthorg/**/*.py : Always use structured logging: `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Use structured logging: always `logger.info(EVENT, key=value)` — never `logger.info("msg %s", val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/**/*.py : `RetryConfig` and `RateLimiterConfig` are set per-provider in `ProviderConfig`. Retryable errors: `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately.

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/__init__.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : All state transitions must log at INFO.

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use DEBUG logging for object creation, internal flow, and entry/exit of key functions.

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.

Applied to files:

  • src/synthorg/api/controllers/__init__.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/persistence/sqlite/backend.py
🔇 Additional comments (29)
src/synthorg/observability/events/versioning.py (1)

35-36: Good addition of a dedicated not-found event constant.

This keeps missing-version logs consistent with the versioning event namespace.

As per coding guidelines: "Event names: always use constants from the domain-specific module under synthorg.observability.events."

src/synthorg/persistence/sqlite/version_repo.py (1)

187-188: Docstring improvement is accurate and useful.

Explicitly documenting QueryError here improves the repository contract for callers.

CLAUDE.md (1)

93-93: LGTM — event-convention docs stay aligned with the new versioning events.

web/src/api/types.ts (1)

1782-1793: Type contract update looks correct for the snapshot-based API shape.

web/src/pages/workflow-editor/VersionHistoryPanel.tsx (1)

27-27: Correct field-path update for the new version payload shape.

src/synthorg/engine/workflow/diff.py (1)

283-284: Helper type updates are clean and consistent with VersionSnapshot usage.

Also applies to: 382-383, 422-423

src/synthorg/api/controllers/company.py (1)

100-104: Good propagation of actor identity into mutation versioning.

Passing saved_by from request context improves historical attribution quality for company snapshots.

src/synthorg/api/controllers/__init__.py (1)

15-17: LGTM!

The new version controller imports and registrations are correctly structured. The controllers are properly added to both ALL_CONTROLLERS for route registration and __all__ for public API export. The alphabetical ordering in __all__ is maintained.

Also applies to: 23-25, 31-33, 46-46, 99-102, 119-119, 124-124, 129-129, 140-140

src/synthorg/api/services/org_mutations.py (1)

234-290: LGTM - Budget snapshot correctly placed inside org lock.

The _snapshot_budget_config call at lines 287-288 is correctly positioned inside the _org_lock context, ensuring atomicity between the budget update and version snapshot creation. The saved_by parameter threading from the public API is well-designed for audit attribution.

src/synthorg/api/controllers/company_versions.py (3)

39-64: LGTM - List versions endpoint is well-structured.

The pagination implementation correctly uses asyncio.gather for concurrent fetch/count operations, constructs proper PaginationMeta, and logs appropriately at DEBUG level for successful listings.


66-93: LGTM - Get single version endpoint handles 404 correctly.

The endpoint properly validates version_num >= 1 via Parameter(ge=1), logs warnings for missing versions, and returns a structured 404 response with an error message.


33-37: No action needed. CompanyVersionController and CompanyController do not have conflicting routes: CompanyController exposes GET /company, PATCH /company, POST /company/reorder-departments, and GET /company/departments, while CompanyVersionController exposes GET /company/versions and GET /company/versions/{version_num}. These paths are completely distinct and coexist without collision.

			> Likely an incorrect or invalid review comment.
src/synthorg/api/controllers/evaluation_config_versions.py (1)

1-93: LGTM!

The EvaluationConfigVersionController follows the same well-structured pattern as other version controllers. The unique path /evaluation/config avoids conflicts, pagination and 404 handling are correctly implemented, and logging uses proper event constants.

tests/unit/persistence/sqlite/test_entity_version_repos.py (2)

1-6: LGTM - Company round-trip test now included.

The test suite now comprehensively covers all five entity types mentioned in the docstring (WorkflowDefinition, BudgetConfig, EvaluationConfig, Company, Role). The test_company_roundtrip test validates the full save/retrieve cycle.

Also applies to: 187-202


208-232: LGTM - Duplicate PK idempotency test validates INSERT OR IGNORE.

The test_duplicate_pk_idempotency test correctly verifies that:

  1. Initial insert returns True
  2. Different version number allows insertion
  3. Duplicate (entity_id, version) returns False (ignored)
  4. Final count is accurate

This confirms the INSERT OR IGNORE semantics for concurrent-write safety.

src/synthorg/persistence/sqlite/backend.py (2)

278-317: LGTM - Factory helper eliminates duplication.

The _ver_repo factory cleanly encapsulates the SQLiteVersionRepository instantiation pattern with consistent JSON serialization/deserialization via model_dump(mode="json") and model_validate(). This addresses the previous review suggestion about reducing repetition.


653-707: All version repository table names correctly match the schema definitions. Each _ver_repo() call explicitly passes the table name as the first argument, which aligns with the corresponding CREATE TABLE statements in schema.sql:

  • evaluation_config_versions
  • budget_config_versions
  • company_versions
  • role_versions

No action needed.

src/synthorg/api/controllers/role_versions.py (2)

30-62: LGTM - Per-role versioning correctly uses role_name as entity_id.

Unlike singleton config controllers that use "default" as entity_id, this controller correctly uses role_name as the entity identifier, supporting the per-role granularity design decision mentioned in the PR objectives. The list endpoint properly passes role_name to both list_versions and count_versions.


64-92: LGTM - 404 error message includes role context.

The error message at line 86 helpfully includes role_name in the response (f"Version {version_num} not found for role {role_name!r}"), providing better debugging context than generic version-not-found messages.

tests/unit/api/controllers/test_config_version_controllers.py (3)

135-220: LGTM - Company version tests now include comprehensive coverage.

The TestCompanyVersions class now includes test_list_versions_with_data and test_list_versions_paginated, addressing the previous review comment about missing coverage parity with TestBudgetConfigVersions.


226-290: LGTM - EvaluationConfig version tests include list with data.

The TestEvaluationConfigVersions class now includes test_list_versions_with_data, addressing the previous review comment.


296-377: LGTM - Role version tests verify per-role entity_id pattern.

The TestRoleVersions class correctly tests the per-role versioning pattern where role_name (e.g., "backend-dev") is used as the entity identifier. The tests properly use _snap("backend-dev", role, ...) to match this design.

src/synthorg/api/controllers/budget_config_versions.py (3)

1-31: LGTM! Imports, logging setup, and module constants are well-structured.

The file correctly:

  • Uses get_logger(__name__) per guidelines
  • Imports event constants from the versioning events module
  • Defines a clear type alias for the snapshot type
  • Uses a descriptive constant for the singleton entity ID

39-64: LGTM! The list_versions endpoint is correctly implemented.

Good use of asyncio.gather for concurrent fetching of versions and count. The DEBUG logging with structured kwargs follows guidelines. Pagination parameters and response wrapping are consistent with the generic versioning pattern.


66-93: LGTM! The get_version endpoint correctly handles the not-found case.

The WARNING log before returning 404 satisfies the guideline that all error paths must log at WARNING or ERROR with context. Parameter validation with ge=1 prevents invalid version numbers at the API boundary.

src/synthorg/persistence/sqlite/schema.sql (4)

604-621: LGTM! The evaluation_config_versions table follows the established pattern.

Table structure, CHECK constraints, and indexes are consistent with the generic VersionSnapshot[T] infrastructure.


623-640: LGTM! The budget_config_versions table is correctly defined.

Structurally identical to other version tables with proper constraints and indexes.


642-659: LGTM! The company_versions table follows the versioning pattern.

Consistent with the other new version tables.


661-678: LGTM! The role_versions table completes the versioning infrastructure.

All five version tables now use the same generic schema pattern, enabling uniform SQLiteVersionRepository[T] usage across entity types.

Comment on lines +91 to +114
async def _snapshot_budget_config(self, saved_by: str) -> None:
"""Snapshot the current BudgetConfig if content changed.

Best-effort: versioning failures are logged but do not
block the mutation.

Args:
saved_by: Actor who triggered the mutation.
"""
if self._budget_versioning is None:
return
try:
budget = await self._resolver.get_budget_config()
await self._budget_versioning.snapshot_if_changed(
entity_id="default",
snapshot=budget,
saved_by=saved_by,
)
except PersistenceError:
logger.exception(
VERSION_SNAPSHOT_FAILED,
entity_type="BudgetConfig",
entity_id="default",
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Best-effort versioning may not handle all failure modes.

The _snapshot_budget_config method only catches PersistenceError, but self._resolver.get_budget_config() at line 103 may raise SettingNotFoundError or other resolver exceptions. If this is truly best-effort (as documented), consider broadening the exception handling:

🛡️ Proposed fix to catch all versioning errors
         try:
             budget = await self._resolver.get_budget_config()
             await self._budget_versioning.snapshot_if_changed(
                 entity_id="default",
                 snapshot=budget,
                 saved_by=saved_by,
             )
-        except PersistenceError:
+        except (PersistenceError, Exception) as exc:
             logger.exception(
                 VERSION_SNAPSHOT_FAILED,
                 entity_type="BudgetConfig",
                 entity_id="default",
             )

Alternatively, if resolver failures should propagate to indicate a more serious configuration issue, document this explicitly in the docstring.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/services/org_mutations.py` around lines 91 - 114, The
_snapshot_budget_config method currently only catches PersistenceError but calls
self._resolver.get_budget_config which can raise SettingNotFoundError or other
resolver exceptions; update the try/except to either catch SettingNotFoundError
in addition to PersistenceError or broaden to a general Exception to preserve
the "best-effort" behavior, and when logging VERSION_SNAPSHOT_FAILED include the
caught exception info (keep existing entity_type/entity_id context) so resolver
failures don't crash the mutation unless you intend them to—adjust the docstring
if you choose to let resolver errors propagate.

@Aureliolo Aureliolo force-pushed the feat/versioning-generalization branch from b7018c8 to 914a4b3 Compare April 8, 2026 19:10
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 8, 2026 19:11 — with GitHub Actions Inactive
@Aureliolo
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/persistence/sqlite/schema.sql`:
- Around line 437-469: The comment warns that the new CREATE TABLE IF NOT EXISTS
for workflow_definition_versions will silently do nothing if an old-schema table
exists; add an explicit safety/migration check and guidance: implement a startup
or migration script that checks for the legacy table name/columns (e.g.,
existence of _wdv_old or columns like definition_id, workflow_type), refuse to
apply the new schema or emit a clear error/warning and instructions if legacy
data is detected, and document or provide an automated migration path that
computes content_hash and populates the new workflow_definition_versions (and
then creates/validates idx_wdv_entity_saved and idx_wdv_content_hash) so
operators are prevented from accidentally running with incompatible schemas.

In `@tests/unit/api/controllers/test_config_version_controllers.py`:
- Around line 226-290: Add a pagination unit test for
TestEvaluationConfigVersions mirroring the pattern used in
TestBudgetConfigVersions/TestCompanyVersions: create multiple versions via
fake_persistence.evaluation_config_versions using await
repo.save_version(_snap(...)) (use EvaluationConfig instances), then call the
GET endpoint "/api/v1/evaluation/config/versions" with pagination query params
(e.g., limit and cursor/next) and assert the response status is 200, the "data"
length equals the page size, ordering is newest-first (versions descending), and
that the response includes pagination metadata (cursor/next) consistent with
other tests; name the test test_list_versions_paginated and place it alongside
the other methods in class TestEvaluationConfigVersions.

In `@tests/unit/api/fakes_workflow.py`:
- Around line 136-137: Update the type annotations for all entity_id parameters
in the fake workflow to use NotBlankStr instead of str: change signatures in the
fake functions/methods including get_latest_version, get_by_content_hash,
list_versions, count_versions, delete_versions_for_entity (and the earlier
occurrence at the initial constructor/signature around the provided diff) so
they match the protocol’s NotBlankStr type and improve IDE/type-checking
support; ensure any imports for NotBlankStr are added if missing.

In `@tests/unit/persistence/sqlite/test_entity_version_repos.py`:
- Around line 238-260: In test_role_roundtrip, replace the hardcoded string
assertion comparing loaded.snapshot.department to "engineering" with the enum
constant to ensure type safety: update the assertion in test_role_roundtrip (the
block that creates Role, saves via repo.save_version and loads via
repo.get_version) to assert loaded.snapshot.department ==
DepartmentName.ENGINEERING so the test uses the DepartmentName enum rather than
a raw string.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c45a773a-7503-40e5-b3e0-5f2ece2dbd14

📥 Commits

Reviewing files that changed from the base of the PR and between b7018c8 and 914a4b3.

📒 Files selected for processing (34)
  • CLAUDE.md
  • docs/design/engine.md
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/_workflow_helpers.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/state.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/engine/workflow/version.py
  • src/synthorg/observability/events/versioning.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/persistence/sqlite/schema.sql
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/persistence/sqlite/workflow_version_repo.py
  • src/synthorg/persistence/workflow_version_repo.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/api/fakes_backend.py
  • tests/unit/api/fakes_workflow.py
  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/engine/workflow/test_version.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/persistence/sqlite/test_workflow_version_repo.py
  • tests/unit/persistence/test_protocol.py
  • web/src/api/types.ts
  • web/src/pages/workflow-editor/VersionHistoryPanel.tsx
💤 Files with no reviewable changes (6)
  • src/synthorg/api/controllers/_workflow_helpers.py
  • tests/unit/persistence/sqlite/test_workflow_version_repo.py
  • src/synthorg/engine/workflow/version.py
  • src/synthorg/persistence/workflow_version_repo.py
  • src/synthorg/persistence/sqlite/workflow_version_repo.py
  • tests/unit/engine/workflow/test_version.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (7)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations.

Use PEP 758 except syntax: use except A, B: (no parentheses) -- ruff enforces this on Python 3.14.

Type hints required on all public functions; enforce mypy strict mode.

Docstrings required on public classes and functions in Google style; enforced by ruff D rules.

Create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement. For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Distinguish config vs runtime state: use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves (e.g. agent execution state, task progress). Never mix static config fields with mutable runtime fields in one model.

Use Pydantic v2 with BaseModel, model_validator, computed_field, and ConfigDict. Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time.

Use @computed_field for derived values instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens).

Use NotBlankStr (from core.types) for all identifier/name fields -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators.

Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task. Existing code is being migrated incrementally.

Line length: 88 characters (enforced by ruff).

Functions must be < 50 lines; files must be < 800...

Files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/observability/events/versioning.py
  • src/synthorg/api/state.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • src/synthorg/persistence/sqlite/version_repo.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/api/fakes_backend.py
  • tests/unit/engine/workflow/test_diff.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/services/org_mutations.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • src/synthorg/persistence/sqlite/backend.py
  • tests/unit/persistence/test_protocol.py
  • tests/unit/api/fakes_workflow.py
  • src/synthorg/api/controllers/__init__.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__).

Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, observability/http_handler.py, and observability/otlp_handler.py may use stdlib logging and print(..., file=sys.stderr) for handler construction, bootstrap, and error reporting code that runs before or during logging system configuration.

Use constants from domain-specific modules under synthorg.observability.events for event names (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT. Never use hardcoded strings for event names.

Use structured logging: always logger.info(EVENT, key=value) -- never logger.info("msg %s", val).

All error paths must log at WARNING or ERROR with context before raising.

All state transitions must log at INFO level.

Use DEBUG logging for object creation, internal flow, entry/exit of key functions.

All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code -- it's handled by the base class.

RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig.

Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately without retry. RetryExhaustedError signals that all retries failed -- the engine layer catches this to trigger fallback chains. Rate limiter respects RateLimitError.retry_after from providers -- automatically pauses future requests.

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, o...

Files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/observability/events/versioning.py
  • src/synthorg/api/state.py
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/__init__.py
src/**/*.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/observability/events/versioning.py
  • src/synthorg/api/state.py
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/__init__.py
web/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (web/CLAUDE.md)

web/src/**/*.{ts,tsx,js,jsx}: Always use createLogger from @/lib/logger -- never bare console.warn/console.error/console.debug in application code
Logger variable name must always be const log (e.g. const log = createLogger('module-name'))
Pass dynamic/untrusted values as separate arguments to logger methods (not interpolated into the message string) so they go through sanitizeArg
Attacker-controlled fields inside structured objects must be wrapped in sanitizeForLog() before embedding in log calls

Files:

  • web/src/pages/workflow-editor/VersionHistoryPanel.tsx
  • web/src/api/types.ts
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (web/CLAUDE.md)

web/src/**/*.{ts,tsx}: Use Tailwind semantic classes (text-foreground, bg-card, text-accent, text-success, bg-danger, etc.) or CSS variables (var(--so-*)) for colors; NEVER hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (Geist tokens) for typography; NEVER set fontFamily directly in .tsx/.ts files
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; NEVER hardcode pixel values for layout spacing in components
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows and borders; NEVER hardcode values in .tsx/.ts files
Use @/lib/motion presets for Framer Motion transition durations; NEVER hardcode transition durations
CSS side-effect imports in TypeScript 6 require type declarations -- add /// <reference types="vite/client" /> at the top of files with CSS imports

Always reuse existing components from web/src/components/ui/ before creating new ones. Never hardcode hex colors, font-family, pixel spacing, or Framer Motion transitions -- use design tokens and @/lib/motion presets. A PostToolUse hook (scripts/check_web_design_system.py) enforces these rules on every Edit/Write to web/src/.

Follow the full component inventory, design token rules, and post-training references in web/CLAUDE.md (TS6, Storybook 10).

Files:

  • web/src/pages/workflow-editor/VersionHistoryPanel.tsx
  • web/src/api/types.ts
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Always read the relevant docs/design/ page before implementing any feature or planning any issue. The design spec is the starting point for architecture, data models, and behavior. When a spec topic is referenced, read the relevant docs/design/ page before coding.

Files:

  • CLAUDE.md
  • docs/design/engine.md
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Mark tests with @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, or @pytest.mark.slow.

Maintain 80% code coverage minimum (enforced in CI).

Use asyncio_mode = "auto" for async tests -- no manual @pytest.mark.asyncio needed.

Timeout is 30 seconds per test (global in pyproject.toml) -- do not add per-file pytest.mark.timeout(30) markers. Non-default overrides like timeout(60) are allowed.

Always use -n 8 with pytest for parallelism via pytest-xdist, never run tests sequentially. CI uses -n auto (fewer cores on runners).

Prefer @pytest.mark.parametrize for testing similar cases.

Use test-provider, test-small-001, etc. in tests instead of real vendor names.

Use Hypothesis (@given + @settings) for property-based testing in Python. Hypothesis profiles: ci (deterministic, max_examples=10 + derandomize=True), dev (1000 examples), fuzz (10,000 examples, no deadline), extreme (500,000 examples, no deadline). Control via HYPOTHESIS_PROFILE env var. Failing examples are persisted to ~/.synthorg/hypothesis-examples/. When Hypothesis finds a failure, fix the underlying bug and add an @example(...) decorator to permanently cover the case in CI.

Files:

  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/api/fakes_backend.py
  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/persistence/test_protocol.py
  • tests/unit/api/fakes_workflow.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/api/fakes_backend.py
  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/persistence/test_protocol.py
  • tests/unit/api/fakes_workflow.py
🧠 Learnings (44)
📓 Common learnings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:36:20.083Z
Learning: If implementation deviates from the spec (better approach found, scope evolved, etc.), alert the user and explain why -- user decides whether to proceed or update the spec. Do NOT silently diverge -- every deviation needs explicit user approval.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:36:20.083Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:36:20.083Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding starts. At every phase of planning and implementation, be critical -- actively look for ways to improve the design in the spirit of what we're building (robustness, correctness, simplicity, future-proofing where it's free).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:36:20.083Z
Learning: Prioritize issues by dependency order, not priority labels -- unblocked dependencies come first.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:36:20.083Z
Learning: Pure data models, enums, and re-exports do NOT need logging.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:36:20.083Z
Learning: Never skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins. For tasks that must block indefinitely until cancelled, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)` -- it is cancellation-safe and carries no timing assumptions.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:36:20.083Z
Learning: Commits: `<type>: <description>` format (types: feat, fix, refactor, docs, test, chore, perf, ci). Enforced by commitizen (commit-msg hook). Signed commits required on `main` via branch protection -- all commits must be GPG/SSH signed.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:36:20.083Z
Learning: Branch naming: `<type>/<slug>` from main.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:36:20.083Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint (Dockerfile linting), golangci-lint + go vet (CLI, conditional on `cli/**/*.go`), no-em-dashes, no-redundant-timeout, eslint-web (web dashboard, zero warnings, conditional on `web/src/**/*.{ts,tsx}`).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:36:20.083Z
Learning: Pre-push hooks: mypy type-check (affected modules only) + pytest unit tests (affected modules only) + golangci-lint + go vet + go test (CLI, conditional on `cli/**/*.go`) + eslint-web (web dashboard) (fast gate before push, skipped in pre-commit.ci -- dedicated CI jobs already run these). Foundational module changes (core, config, observability) or conftest changes trigger full runs.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:36:20.083Z
Learning: Never create a PR directly with `gh pr create` -- it is blocked by hookify. Always use `/pre-pr-review` to create PRs -- it runs automated checks + review agents + fixes before creating the PR. For trivial/docs-only changes: `/pre-pr-review quick` skips agents but still runs automated checks.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:36:20.083Z
Learning: After finishing an issue implementation, always create a feature branch (`<type>/<slug>`), commit, and push -- do NOT create a PR automatically. Do NOT leave work uncommitted on main -- branch, commit, push immediately after finishing.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:36:20.083Z
Learning: Fix everything valid found during review -- never skip. When review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes), fix them all. No deferring, no 'out of scope' skipping.
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers

Applied to files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/__init__.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/api/**/*.py : REST API: Litestar framework, controllers with guards, channels for WebSocket, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint. RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation).

Applied to files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/workflow_versions.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/__init__.py
📚 Learning: 2026-04-06T16:35:12.934Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-06T16:35:12.934Z
Learning: All project conventions, commands, and standards are defined in CLAUDE.md - refer to it for project structure, package layout, code conventions, quick commands, git workflow, testing standards, design specifications, logging, resilience, and security patterns

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-31T16:09:24.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T16:09:24.320Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from `synthorg.observability.events.<domain>` modules (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly and use in structured logging

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-04-02T12:07:44.443Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T12:07:44.443Z
Learning: Applies to src/synthorg/**/*.py : Always use structured logging: `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Use structured logging: always `logger.info(EVENT, key=value)` — never `logger.info("msg %s", val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/api/state.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/api/state.py
📚 Learning: 2026-03-30T10:41:40.176Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T10:41:40.176Z
Learning: Update the relevant `docs/design/` page to reflect new reality when approved deviations from spec occur

Applied to files:

  • docs/design/engine.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.

Applied to files:

  • docs/design/engine.md
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: Applies to docs/design/*.md : Update the relevant `docs/design/` page when approved deviations occur to reflect the new reality

Applied to files:

  • docs/design/engine.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/engine.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/persistence/protocol.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/__init__.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-04-08T20:20:05.318Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T20:20:05.318Z
Learning: Applies to src/synthorg/**/*.py : If implementation deviates from the design spec (better approach found, scope evolved, etc.), alert the user and explain why before proceeding; do NOT silently diverge

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/**/*.py : `RetryConfig` and `RateLimiterConfig` are set per-provider in `ProviderConfig`. Retryable errors: `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately.

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/synthorg/api/**/*.py : Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Applied to files:

  • src/synthorg/api/controllers/workflow_versions.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/api/**/*.py : Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.

Applied to files:

  • src/synthorg/api/controllers/workflow_versions.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : All state transitions must log at INFO.

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use DEBUG logging for object creation, internal flow, and entry/exit of key functions.

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : All state transitions must log at INFO level

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to {pyproject.toml,src/synthorg/__init__.py} : Update version in `pyproject.toml` (`[tool.commitizen].version`) and `src/synthorg/__init__.py` (`__version__`)

Applied to files:

  • src/synthorg/api/controllers/__init__.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.

Applied to files:

  • src/synthorg/api/controllers/__init__.py
🔇 Additional comments (35)
src/synthorg/observability/events/versioning.py (1)

35-36: Version-not-found event constant is correctly introduced.

Line 35 and Line 36 follow the established versioning event naming and documentation pattern.

src/synthorg/persistence/sqlite/version_repo.py (1)

186-188: save_version() error contract is now accurately documented.

Line 187 and Line 188 correctly describe the QueryError raise conditions.

src/synthorg/api/controllers/company.py (1)

14-14: Actor attribution is correctly propagated in company updates.

Line 103 now forwards authenticated user identity, which keeps version snapshots attributable to the caller.

Also applies to: 103-103

web/src/pages/workflow-editor/VersionHistoryPanel.tsx (1)

27-27: Version title binding is aligned with the updated API payload.

Line 27 correctly reads from v.snapshot.name.

CLAUDE.md (1)

93-93: Logging conventions doc is updated consistently with new versioning events.

Line 93 now includes VERSION_LISTED and VERSION_NOT_FOUND, keeping guidance current.

src/synthorg/api/state.py (1)

204-208: OrgMutationService wiring is now consistent across both construction paths.

Passing budget_config_versions in both locations avoids startup/deferred-init behavior drift.

Also applies to: 845-849

docs/design/engine.md (1)

228-231: Version-history design section now matches the implemented generic snapshot model.

The updated schema and behavior notes are accurate and complete for the new flow.

tests/unit/api/controllers/test_workflow_versions.py (1)

146-147: Assertions are correctly updated to the nested snapshot response format.

These changes align test expectations with the VersionSnapshot-based API payload.

Also applies to: 194-194, 284-284

src/synthorg/engine/workflow/diff.py (2)

195-206: LGTM - Snapshot ID consistency check correctly implemented.

This addresses the past review feedback by adding a secondary validation that old.snapshot.id == new.snapshot.id, preventing diffs when the payload's internal ID doesn't match the entity context. The error message is descriptive and includes both snapshot IDs for debugging.


167-221: LGTM - compute_diff correctly migrated to VersionSnapshot.

The function signature and implementation properly:

  1. Accept VersionSnapshot[WorkflowDefinition] for both parameters
  2. Validate entity-level and snapshot-level ID consistency
  3. Extract old.snapshot/new.snapshot for the inner diff operations
  4. Populate WorkflowDiff.definition_id from old.entity_id
src/synthorg/api/controllers/workflows.py (2)

421-435: LGTM - Correct migration to VersioningService.

The snapshot creation now correctly routes through VersioningService.snapshot_if_changed(), which handles content-hash deduplication and concurrent-write conflict resolution. Ignoring the return value is intentional per the design - versioning is fire-and-forget, and failures are logged but don't block the primary workflow creation.


614-617: LGTM - Correct method rename for generic version repository.

The method rename from delete_versions_for_definition to delete_versions_for_entity aligns with the generic VersionRepository[T] interface that now handles multiple entity types.

tests/unit/persistence/sqlite/test_migrations.py (1)

45-48: LGTM - Schema expectations correctly updated.

The new version tables and their corresponding indexes follow the established pattern from the existing workflow_definition_versions and agent_identity_versions tables. Each table has the expected (entity_id, saved_at DESC) and (entity_id, content_hash) indexes for efficient version listing and content-hash deduplication.

Also applies to: 108-109, 124-131

tests/unit/engine/workflow/test_diff.py (1)

56-89: LGTM - Test helper correctly migrated to VersionSnapshot.

The _ver() helper properly constructs VersionSnapshot[WorkflowDefinition] by:

  1. Building the inner WorkflowDefinition with correct field mapping
  2. Computing content_hash via compute_content_hash()
  3. Ensuring definition.id matches entity_id to satisfy the new snapshot consistency check in compute_diff()
web/src/api/types.ts (1)

1781-1796: LGTM - Type correctly aligned with backend VersionSnapshot model.

The restructured type properly separates:

  • Version metadata at top level: entity_id, version, content_hash, saved_by, saved_at
  • Entity payload in nested snapshot: workflow-specific fields (id, name, description, etc.)

This matches the backend VersionSnapshot[WorkflowDefinition] structure and the frontend usage in VersionHistoryPanel.tsx which correctly accesses v.snapshot.name.

tests/unit/api/fakes_backend.py (1)

236-239: LGTM - Fake backend correctly extended for new version repositories.

The additions follow the established pattern from identity_versions, ensuring test coverage for the new EvaluationConfig, BudgetConfig, Company, and Role versioning surfaces.

Also applies to: 375-389

src/synthorg/api/controllers/__init__.py (1)

99-102: LGTM - New version controllers correctly registered.

The four new version history controllers are properly:

  1. Imported from their respective modules
  2. Added to ALL_CONTROLLERS tuple for Litestar registration
  3. Included in __all__ for public export

Also applies to: 119-119, 124-124, 129-129, 140-140

src/synthorg/persistence/protocol.py (1)

263-295: LGTM - Protocol correctly extended with generic version repositories.

The migration from WorkflowVersionRepository to VersionRepository[WorkflowDefinition] and the addition of four new typed version repository properties follow the established pattern. SQLiteBackend correctly implements all five version repository properties with proper return type annotations (SQLiteVersionRepository[T]) and docstrings.

src/synthorg/api/services/org_mutations.py (2)

91-114: LGTM! Exception handling correctly addresses both failure modes.

The _snapshot_budget_config helper now catches both PersistenceError (which includes QueryError per the exception hierarchy) and SettingNotFoundError from the resolver. This implements proper best-effort semantics where versioning failures are logged but don't block mutations.


287-288: Budget snapshot trigger looks correct.

The conditional snapshot is appropriately placed inside the lock after the budget value is persisted, ensuring consistency between the settings write and the version snapshot.

src/synthorg/api/controllers/role_versions.py (1)

1-92: LGTM! Controller follows the established version-history pattern.

The RoleVersionController correctly implements per-role versioning with:

  • Proper guards (require_read_access)
  • Concurrent fetching via asyncio.gather for list endpoint
  • Appropriate logging at debug/warning levels
  • Parameter validation (ge=1) for version numbers
tests/unit/persistence/test_protocol.py (1)

577-591: LGTM! Fake backend extended with new version repository properties.

The new properties follow the established pattern of returning object() to satisfy the @runtime_checkable protocol conformance test, consistent with other version repository properties.

src/synthorg/api/controllers/evaluation_config_versions.py (1)

1-93: LGTM! Consistent implementation of evaluation config version history.

The controller follows the established pattern with proper logging, guards, pagination, and error handling matching the other version controllers in this PR.

src/synthorg/api/controllers/company_versions.py (1)

1-93: LGTM! Consistent implementation of company version history.

The controller correctly implements the version-history pattern with singleton "default" entity ID for single-tenant company structure versioning.

tests/unit/api/controllers/test_config_version_controllers.py (1)

25-37: LGTM! Clean helper function using modern Python generics.

The _snap helper uses Python 3.12+ generic syntax [T: BaseModel] and correctly constructs deterministic VersionSnapshot instances for testing.

src/synthorg/api/controllers/budget_config_versions.py (1)

1-93: LGTM! Budget config version controller with proper logging.

The controller correctly implements version history endpoints with structured logging using VERSION_LISTED and VERSION_NOT_FOUND event constants, addressing the previous review feedback.

src/synthorg/api/controllers/workflow_versions.py (2)

181-189: LGTM! Clean model restoration from snapshot.

Using target.snapshot.model_copy(update={...}) elegantly restores the workflow content while preserving identity fields (id, created_by, created_at) and bumping the version.


364-376: Content-hash deduplication for rollback is intentional design.

Per docs/design/engine.md, the versioning system uses content-addressable snapshots where VersioningService.snapshot_if_changed() intentionally skips creating new records when content is unchanged. Rolling back to content matching the current version produces no new version record—this is documented as the intended behavior, not a gap. The rollback action remains logged at INFO level; version history tracks unique content states, not mutation actions.

src/synthorg/persistence/sqlite/backend.py (2)

277-317: LGTM! Factory helper pattern cleanly eliminates duplication.

The _ver_repo helper addresses the previous review feedback by consolidating the serialize/deserialize boilerplate. Each lambda correctly captures its own model_cls from the function parameter scope.


653-707: Property accessors follow established patterns.

The new version repository properties correctly use _require_connected() and maintain consistency with existing accessors. Documentation follows the same style.

tests/unit/persistence/sqlite/test_entity_version_repos.py (3)

21-33: Test schema template is sufficient for unit testing.

The template correctly mirrors production column structure, constraints, and primary key. The omitted indexes (idx_*_entity_saved, idx_*_content_hash) don't affect correctness—they're query optimizations. Unit tests validating repository logic don't require indexes.


73-127: Comprehensive workflow round-trip test.

Test validates nested structures (nodes, edges) survive JSON serialization and content hash is preserved. Good coverage of WorkflowDefinition complexity.


208-232: Idempotency test correctly validates INSERT OR IGNORE semantics.

Verifies that duplicate (entity_id, version) returns False while same content with different version succeeds. Good edge case coverage.

tests/unit/api/fakes_workflow.py (1)

112-186: Protocol implementation is complete and correct.

All seven VersionRepository[WorkflowDefinition] protocol methods are implemented. The storage migration from (definition_id, version) to (entity_id, version) keys aligns with the new generic snapshot model. Proper use of copy.deepcopy() maintains immutability guarantees.

src/synthorg/persistence/sqlite/schema.sql (1)

612-686: New version tables follow consistent schema pattern.

All four tables (evaluation_config_versions, budget_config_versions, company_versions, role_versions) correctly replicate the established VersionSnapshot[T] schema with:

  • Composite primary key (entity_id, version)
  • Non-empty CHECK constraints on text fields
  • UTC timestamp validation on saved_at
  • Appropriate indexes for query patterns

CREATE TABLE IF NOT EXISTS ensures idempotent creation on both fresh installs and upgrades.

Comment on lines 437 to +469
-- ── Workflow Definition Versions ─────────────────────────────
-- Breaking change from v0.6.x: migrated from bespoke flattened
-- columns (definition_id, name, description, workflow_type, nodes,
-- edges, created_by) to generic VersionSnapshot format (entity_id,
-- content_hash, snapshot JSON, saved_by, saved_at).
-- Manual migration for existing databases:
-- 1. ALTER TABLE workflow_definition_versions RENAME TO _wdv_old;
-- 2. Create the new table (below) and indexes.
-- 3. INSERT INTO workflow_definition_versions (entity_id, version,
-- content_hash, snapshot, saved_by, saved_at)
-- SELECT definition_id, version,
-- '<compute-sha256-of-snapshot>', json_object(...old columns...),
-- created_by, saved_at FROM _wdv_old;
-- 4. DROP TABLE _wdv_old;
-- FK to workflow_definitions intentionally dropped for generic
-- VersionSnapshot[T] pattern consistency across all entity types.

CREATE TABLE IF NOT EXISTS workflow_definition_versions (
definition_id TEXT NOT NULL CHECK(length(definition_id) > 0),
entity_id TEXT NOT NULL CHECK(length(entity_id) > 0),
version INTEGER NOT NULL CHECK(version >= 1),
name TEXT NOT NULL CHECK(length(name) > 0),
description TEXT NOT NULL DEFAULT '',
workflow_type TEXT NOT NULL CHECK(workflow_type IN (
'sequential_pipeline', 'parallel_execution', 'kanban', 'agile_kanban'
)),
nodes TEXT NOT NULL,
edges TEXT NOT NULL,
created_by TEXT NOT NULL CHECK(length(created_by) > 0),
content_hash TEXT NOT NULL CHECK(length(content_hash) > 0),
snapshot TEXT NOT NULL CHECK(length(snapshot) > 0),
saved_by TEXT NOT NULL CHECK(length(saved_by) > 0),
saved_at TEXT NOT NULL,
PRIMARY KEY (definition_id, version),
FOREIGN KEY (definition_id)
REFERENCES workflow_definitions(id)
ON DELETE CASCADE
saved_at TEXT NOT NULL CHECK(
saved_at LIKE '%+00:00' OR saved_at LIKE '%Z'
),
PRIMARY KEY (entity_id, version)
);

CREATE INDEX IF NOT EXISTS idx_wdv_definition_saved
ON workflow_definition_versions(definition_id, saved_at DESC);
CREATE INDEX IF NOT EXISTS idx_wdv_entity_saved
ON workflow_definition_versions(entity_id, saved_at DESC);
CREATE INDEX IF NOT EXISTS idx_wdv_content_hash
ON workflow_definition_versions(entity_id, content_hash);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Migration guidance is clear but manual.

The inline comment (lines 438-451) documents the breaking schema change and provides manual migration steps. This aligns with the file header stating "When data stability is declared, adopt Atlas for declarative migrations."

For production deployments, ensure operators are aware that existing workflow_definition_versions data requires manual transformation before this schema can be applied—CREATE TABLE IF NOT EXISTS will silently skip creation if the old-schema table exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/persistence/sqlite/schema.sql` around lines 437 - 469, The
comment warns that the new CREATE TABLE IF NOT EXISTS for
workflow_definition_versions will silently do nothing if an old-schema table
exists; add an explicit safety/migration check and guidance: implement a startup
or migration script that checks for the legacy table name/columns (e.g.,
existence of _wdv_old or columns like definition_id, workflow_type), refuse to
apply the new schema or emit a clear error/warning and instructions if legacy
data is detected, and document or provide an automated migration path that
computes content_hash and populates the new workflow_definition_versions (and
then creates/validates idx_wdv_entity_saved and idx_wdv_content_hash) so
operators are prevented from accidentally running with incompatible schemas.

@Aureliolo Aureliolo force-pushed the feat/versioning-generalization branch from 914a4b3 to b04e59a Compare April 8, 2026 20:52
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 8, 2026 20:54 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/synthorg/api/controllers/workflows.py (1)

421-435: 🧹 Nitpick | 🔵 Trivial

Consider caching the VersioningService instance.

The VersioningService is instantiated fresh in create_from_blueprint, create_workflow, and update_workflow. While functionally correct, you could consider caching it on AppState or creating a helper to reduce object allocation overhead. This is a minor optimization opportunity.

♻️ Optional: Extract service creation to a helper or cache on AppState
# Option 1: Helper function at module level
def _get_versioning_service(
    state: State,
) -> VersioningService[WorkflowDefinition]:
    return VersioningService(state.app_state.persistence.workflow_versions)

# Option 2: Cache on AppState during app startup
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/controllers/workflows.py` around lines 421 - 435, Several
call sites (create_from_blueprint, create_workflow, update_workflow) repeatedly
instantiate VersioningService just to call snapshot_if_changed; extract and
reuse a single VersioningService instance to avoid repeated allocations by
either (a) adding a module-level helper function _get_versioning_service(state:
State) -> VersioningService[WorkflowDefinition] that returns
VersioningService(state.app_state.persistence.workflow_versions) and replace the
direct instantiations with calls to _get_versioning_service, or (b) add a cached
attribute on AppState (e.g., app_state.versioning_service or a factory stored at
startup) and use that shared VersioningService in those functions; ensure calls
to snapshot_if_changed remain unchanged and that the chosen cache is initialized
once during app startup to preserve behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/api/services/org_mutations.py`:
- Around line 103-108: The snapshot is using ConfigResolver.get_budget_config()
which reads from the settings namespace `budget.total_monthly`, but
update_company() writes to `company.total_monthly`, so snapshotting via the
resolver can miss the just-written change; modify the _snapshot_budget_config()
call in src/synthorg/api/services/org_mutations.py (the block that calls
self._resolver.get_budget_config() and
self._budget_versioning.snapshot_if_changed) to build the BudgetConfig from the
updated company value and pass that constructed snapshot directly to
self._budget_versioning.snapshot_if_changed (or persist the change through the
same namespace ConfigResolver.get_budget_config() reads) so the snapshot
reflects the exact value written by update_company(); target symbols:
update_company(), _snapshot_budget_config(), ConfigResolver.get_budget_config(),
and budget_versioning.snapshot_if_changed.

In `@src/synthorg/api/state.py`:
- Around line 204-208: The OrgMutationService is only receiving
budget_config_versions and never the new company_versions dependency, so it
never emits company snapshots; update the OrgMutationService constructors (both
instantiation sites that pass budget_config_versions) to accept and pass a
company_versions repository parameter, store it on the service, and then call
the repository to write a company snapshot whenever company-level mutations
occur (e.g., in update_company and any other methods that currently call
_write_departments or _snapshot_budget_config). Ensure the stored field name
matches company_versions and invoke the appropriate write/snapshot method on
that repository alongside existing budget snapshot calls so company version
history is emitted.

In `@src/synthorg/persistence/sqlite/migrations.py`:
- Around line 74-75: The current call to _check_legacy_workflow_versions
silently logs and lets apply_schema() succeed even when the old
workflow_definition_versions schema exists; change this to fail fast: have
_check_legacy_workflow_versions raise a MigrationError (or re-raise a clear
exception) when it detects the pre-generic layout so startup stops rather than
letting SQLiteVersionRepository later fail, and remove the broad except that
hides real PRAGMA/DB errors—only handle the explicit "table does not exist" case
(or let unexpected sqlite exceptions propagate) so real PRAGMA failures surface
instead of being masked.

In `@tests/unit/api/fakes_workflow.py`:
- Around line 153-161: The fake get_by_content_hash implementation returns the
first insertion-order match from self._versions instead of the newest version
like SQLiteVersionRepository.get_by_content_hash; change get_by_content_hash to
collect all snapshots in self._versions whose entity_id and content_hash match
and return a deepcopy of the one with the highest version (e.g., max by the
VersionSnapshot.version attribute) so unit tests mirror the production ordering
and dedup/rollback behavior.
- Around line 163-175: The test fake list_versions function in
tests/unit/api/fakes_workflow.py currently slices when given negative
pagination, hiding invalid-pagination bugs; update the async function
list_versions to validate its parameters and raise ValueError if limit < 0 or
offset < 0 (same behavior as the production repo), before computing matching
from self._versions and returning the tuple of deep-copied
VersionSnapshot[WorkflowDefinition] results.

In `@web/src/api/types.ts`:
- Around line 1782-1797: The WorkflowDefinitionVersionSummary currently
duplicates a generic version envelope; extract a reusable generic alias like
VersionSummary<TSnapshot> (matching backend VersionSnapshot[T]) and replace
WorkflowDefinitionVersionSummary with VersionSummary<WorkflowDefinitionSnapshot>
where WorkflowDefinitionSnapshot is a narrow Pick of WorkflowDefinition fields
(id, name, description, workflow_type, nodes, edges, created_by) to avoid
duplicated envelope shape; update imports/exports and replace the inline
snapshot object type in WorkflowDefinitionVersionSummary with the new snapshot
type and the generic VersionSummary alias so other DTOs can reuse it.

---

Outside diff comments:
In `@src/synthorg/api/controllers/workflows.py`:
- Around line 421-435: Several call sites (create_from_blueprint,
create_workflow, update_workflow) repeatedly instantiate VersioningService just
to call snapshot_if_changed; extract and reuse a single VersioningService
instance to avoid repeated allocations by either (a) adding a module-level
helper function _get_versioning_service(state: State) ->
VersioningService[WorkflowDefinition] that returns
VersioningService(state.app_state.persistence.workflow_versions) and replace the
direct instantiations with calls to _get_versioning_service, or (b) add a cached
attribute on AppState (e.g., app_state.versioning_service or a factory stored at
startup) and use that shared VersioningService in those functions; ensure calls
to snapshot_if_changed remain unchanged and that the chosen cache is initialized
once during app startup to preserve behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 15d4e55f-69a4-42f9-9e7e-f12c5f849836

📥 Commits

Reviewing files that changed from the base of the PR and between 914a4b3 and b04e59a.

📒 Files selected for processing (35)
  • CLAUDE.md
  • docs/design/engine.md
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/_workflow_helpers.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/state.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/engine/workflow/version.py
  • src/synthorg/observability/events/versioning.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/persistence/sqlite/migrations.py
  • src/synthorg/persistence/sqlite/schema.sql
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/persistence/sqlite/workflow_version_repo.py
  • src/synthorg/persistence/workflow_version_repo.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/api/fakes_backend.py
  • tests/unit/api/fakes_workflow.py
  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/engine/workflow/test_version.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/persistence/sqlite/test_workflow_version_repo.py
  • tests/unit/persistence/test_protocol.py
  • web/src/api/types.ts
  • web/src/pages/workflow-editor/VersionHistoryPanel.tsx
💤 Files with no reviewable changes (6)
  • tests/unit/engine/workflow/test_version.py
  • src/synthorg/api/controllers/_workflow_helpers.py
  • tests/unit/persistence/sqlite/test_workflow_version_repo.py
  • src/synthorg/engine/workflow/version.py
  • src/synthorg/persistence/sqlite/workflow_version_repo.py
  • src/synthorg/persistence/workflow_version_repo.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Dashboard Test
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

No from __future__ import annotations — Python 3.14 has PEP 649 native lazy annotations.

Use PEP 758 except syntax: use except A, B: (no parentheses) — ruff enforces this on Python 3.14.

Type hints: all public functions must have type hints. Use mypy strict mode.

Docstrings: Google style, required on public classes and functions. Enforced by ruff D rules.

Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections, use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement.

For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Config vs runtime state: use frozen Pydantic models for config/identity. Use separate mutable-via-copy models for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Models: Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use allow_inf_nan=False in all ConfigDict declarations. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr for all identifier/name fields.

Async concurrency: prefer asyncio.TaskGroup for fan-out/fan-in parallel operations (e.g., multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.

Line length: 88 characters (enforced by ruff).

Functions: less than 50 lines. Files: less than 800 lines.

Errors: handle explicitly, never silently swallow.

Validate at system boundaries (user input, external APIs, config files).

Variable name for logger: always logger (not _logger, not log).

Files:

  • src/synthorg/observability/events/versioning.py
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/state.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • src/synthorg/api/controllers/workflows.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/api/fakes_backend.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/persistence/sqlite/migrations.py
  • tests/unit/persistence/test_protocol.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/api/fakes_workflow.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic must have: from synthorg.observability import get_logger then logger = get_logger(__name__).

Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, observability/http_handler.py, and observability/otlp_handler.py may use stdlib logging and print(..., file=sys.stderr) for handler construction.

Event names: always use constants from domain-specific modules under synthorg.observability.events. Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Structured kwargs for logging: always logger.info(EVENT, key=value) — never logger.info("msg %s", val).

All error paths must log at WARNING or ERROR with context before raising.

All state transitions must log at INFO.

DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do not need logging.

Vendor-agnostic everywhere: never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases.

Files:

  • src/synthorg/observability/events/versioning.py
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/persistence/sqlite/migrations.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/budget_config_versions.py
src/**/*.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/observability/events/versioning.py
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/persistence/sqlite/migrations.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/budget_config_versions.py
web/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (web/CLAUDE.md)

web/src/**/*.{ts,tsx,js,jsx}: Always use createLogger from @/lib/logger -- never bare console.warn/console.error/console.debug in application code
Logger variable name must always be const log (e.g. const log = createLogger('module-name'))
Pass dynamic/untrusted values as separate arguments to logger methods (not interpolated into the message string) so they go through sanitizeArg
Attacker-controlled fields inside structured objects must be wrapped in sanitizeForLog() before embedding in log calls

Files:

  • web/src/pages/workflow-editor/VersionHistoryPanel.tsx
  • web/src/api/types.ts
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (web/CLAUDE.md)

web/src/**/*.{ts,tsx}: Use Tailwind semantic classes (text-foreground, bg-card, text-accent, text-success, bg-danger, etc.) or CSS variables (var(--so-*)) for colors; NEVER hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (Geist tokens) for typography; NEVER set fontFamily directly in .tsx/.ts files
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; NEVER hardcode pixel values for layout spacing in components
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows and borders; NEVER hardcode values in .tsx/.ts files
Use @/lib/motion presets for Framer Motion transition durations; NEVER hardcode transition durations
CSS side-effect imports in TypeScript 6 require type declarations -- add /// <reference types="vite/client" /> at the top of files with CSS imports

Always reuse existing components from web/src/components/ui/ before creating new ones in the web dashboard.

Files:

  • web/src/pages/workflow-editor/VersionHistoryPanel.tsx
  • web/src/api/types.ts
web/src/**/*.{ts,tsx,css}

📄 CodeRabbit inference engine (CLAUDE.md)

Never hardcode hex colors, font-family, pixel spacing, or Framer Motion transitions in the web dashboard — use design tokens and @/lib/motion presets.

Files:

  • web/src/pages/workflow-editor/VersionHistoryPanel.tsx
  • web/src/api/types.ts
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Test markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow.

Async in tests: asyncio_mode = "auto" — no manual @pytest.mark.asyncio needed.

Test timeout: 30 seconds per test (global in pyproject.toml). Do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed.

Parametrize: Prefer @pytest.mark.parametrize for testing similar cases.

Tests must use test-provider, test-small-001, etc. instead of vendor-specific names.

Property-based testing in Python: use Hypothesis (@given + @settings). Profiles configured in tests/conftest.py: ci (deterministic, max_examples=10 + derandomize=True), dev (1000 examples), fuzz (10,000 examples, no deadline), extreme (500,000 examples, no deadline). Controlled via HYPOTHESIS_PROFILE env var.

Never skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep(). For tasks that must block indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/api/fakes_backend.py
  • tests/unit/persistence/test_protocol.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/api/fakes_workflow.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/api/fakes_backend.py
  • tests/unit/persistence/test_protocol.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/api/fakes_workflow.py
🧠 Learnings (47)
📓 Common learnings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 12 design pages.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: When implementation deviates from the design spec, alert the user and explain why. Do not silently diverge. Every deviation needs explicit user approval.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding starts.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: At every phase of planning and implementation, be critical and actively look for ways to improve the design. Surface improvements as suggestions, not silent changes.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Prioritize issues by dependency order, not priority labels. Unblocked dependencies come first.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Coverage: 80% minimum (enforced in CI).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Parallelism: `pytest-xdist` via `-n 8` — always include `-n 8` when running pytest locally, never run tests sequentially. CI uses `-n auto`.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: CI runs 10 deterministic examples per property test (`derandomize=True`). Random fuzzing runs locally: `HYPOTHESIS_PROFILE=dev` (1000 examples) or `HYPOTHESIS_PROFILE=fuzz` (10,000 examples, no deadline).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: When Hypothesis finds a failure, it is a real bug. Add an explicit `example(...)` decorator to the test so the case is permanently covered in CI. Do not just rerun and move on.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Commits: `<type>: <description>` — types: feat, fix, refactor, docs, test, chore, perf, ci. Enforced by commitizen (commit-msg hook).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Signed commits: required on `main` via branch protection — all commits must be GPG/SSH signed.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Branches: `<type>/<slug>` from main.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Pre-commit hooks: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint + go vet (CLI), no-em-dashes, no-redundant-timeout, eslint-web (web dashboard).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Hookify rules (committed in `.claude/hookify.*.md`): `block-pr-create` blocks direct `gh pr create`, `enforce-parallel-tests` enforces `-n 8` with pytest, `no-cd-prefix` blocks `cd` prefix in Bash commands, `no-local-coverage` blocks `--cov` flags locally.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Pre-push hooks: mypy type-check (affected modules) + pytest unit tests (affected modules) + golangci-lint + go vet + go test (CLI) + eslint-web (web dashboard). Foundational module changes (core, config, observability) or conftest changes trigger full runs.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Pre-commit.ci: autoupdate disabled. Dependabot owns hook version bumps via `pre-commit` ecosystem.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: GitHub issue queries: use `gh issue list` via Bash — MCP `list_issues` has unreliable field data.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Merge strategy: squash merge — PR body becomes the squash commit message on main. Trailers (e.g. `Release-As`, `Closes `#N``) must be in the PR body to land in the final commit.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: PR issue references: preserve existing `Closes `#NNN`` references — never remove unless explicitly asked.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: After finishing an issue implementation, always create a feature branch (`<type>/<slug>`), commit, and push — do not create a PR automatically. Do not leave work uncommitted on main.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Never create a PR directly — `gh pr create` is blocked by hookify. Always use `/pre-pr-review` to create PRs. For trivial/docs-only changes: `/pre-pr-review quick` skips agents but still runs automated checks.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Never use `cd` in Bash commands — the working directory is already set to the project root. Use absolute paths or run commands directly. Exception: `bash -c "cd <dir> && <cmd>"` is safe (runs in a child process). Use this for tools without a `-C` flag.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Never use Bash to write or modify files — use the Write or Edit tools. Do not use `cat >`, `cat << EOF`, `echo >`, `echo >>`, `sed -i`, `python -c "open(...).write(...)"`, or `tee` to create or modify files.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: A PostToolUse hook (`scripts/check_web_design_system.py`) enforces web design system rules on every Edit/Write to `web/src/`.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: See `web/CLAUDE.md` for web dashboard commands, design system, and component inventory. See `cli/CLAUDE.md` for CLI commands, flags, and reference. Key rule: use `go -C cli` (never `cd cli`).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T20:53:52.443Z
Learning: Fix everything valid found during review — never skip or defer. When review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes), fix them all.
📚 Learning: 2026-04-06T16:35:12.934Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-06T16:35:12.934Z
Learning: All project conventions, commands, and standards are defined in CLAUDE.md - refer to it for project structure, package layout, code conventions, quick commands, git workflow, testing standards, design specifications, logging, resilience, and security patterns

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-04-02T12:07:44.443Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T12:07:44.443Z
Learning: Applies to src/synthorg/**/*.py : Always use structured logging: `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Use structured logging: always `logger.info(EVENT, key=value)` — never `logger.info("msg %s", val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers

Applied to files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/api/**/*.py : REST API: Litestar framework, controllers with guards, channels for WebSocket, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint. RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation).

Applied to files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/company_versions.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/company_versions.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/api/state.py
📚 Learning: 2026-03-30T10:41:40.176Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T10:41:40.176Z
Learning: Update the relevant `docs/design/` page to reflect new reality when approved deviations from spec occur

Applied to files:

  • docs/design/engine.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.

Applied to files:

  • docs/design/engine.md
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: Applies to docs/design/*.md : Update the relevant `docs/design/` page when approved deviations occur to reflect the new reality

Applied to files:

  • docs/design/engine.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/engine.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/backend.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-04-08T20:20:05.318Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T20:20:05.318Z
Learning: Applies to src/synthorg/**/*.py : If implementation deviates from the design spec (better approach found, scope evolved, etc.), alert the user and explain why before proceeding; do NOT silently diverge

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/**/*.py : `RetryConfig` and `RateLimiterConfig` are set per-provider in `ProviderConfig`. Retryable errors: `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately.

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to {pyproject.toml,src/synthorg/__init__.py} : Update version in `pyproject.toml` (`[tool.commitizen].version`) and `src/synthorg/__init__.py` (`__version__`)

Applied to files:

  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/workflow_versions.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.

Applied to files:

  • src/synthorg/api/controllers/__init__.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/synthorg/api/**/*.py : Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Applied to files:

  • src/synthorg/api/controllers/workflow_versions.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/api/**/*.py : Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.

Applied to files:

  • src/synthorg/api/controllers/workflow_versions.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : All state transitions must log at INFO.

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use DEBUG logging for object creation, internal flow, and entry/exit of key functions.

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : All state transitions must log at INFO level

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators

Applied to files:

  • tests/unit/api/fakes_workflow.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • tests/unit/api/fakes_workflow.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • tests/unit/api/fakes_workflow.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators

Applied to files:

  • tests/unit/api/fakes_workflow.py
📚 Learning: 2026-04-08T20:20:05.318Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T20:20:05.318Z
Learning: Applies to **/*.py : Use `NotBlankStr` from `core.types` for all identifier/name fields (including optional `NotBlankStr | None` and tuple variants `tuple[NotBlankStr, ...]`) instead of manual whitespace validators

Applied to files:

  • tests/unit/api/fakes_workflow.py

@Aureliolo
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.github/workflows/docker.yml (1)

225-237: ⚠️ Potential issue | 🔴 Critical

Enable pipefail before piping cosign into tee.

At lines 230, 432, and 634, the pipeline if ! cosign ... | tee /tmp/cosign-out; then only captures cosign's exit code if pipefail is enabled. These steps do not set shell: bash; GitHub Actions defaults to bash -e {0} (errexit enabled, pipefail disabled). If cosign sign fails but writes output, tee exits 0, causing the pipeline to succeed and bypassing the error handler. This allows unsigned backend, web, and sandbox images to be published without detection.

Suggested fix

Apply the same change to all three Sign image steps:

        run: |
+         set -o pipefail
          if [ -z "$DIGEST" ]; then
            echo "::error::Push step did not produce a digest -- cannot sign image"
            exit 1
          fi
          if ! cosign sign --yes ghcr.io/aureliolo/synthorg-backend@${DIGEST} 2>&1 | tee /tmp/cosign-out; then
            if grep -q 'createLogEntryConflict' /tmp/cosign-out; then
              echo "::notice::Image already signed in transparency log -- skipping"
            else
              cat /tmp/cosign-out
              exit 1
            fi
          fi

Also applies to: lines 427–439 (web), 629–641 (sandbox).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/docker.yml around lines 225 - 237, The cosign signing
pipeline (the command using DIGEST, cosign sign and piping to tee writing
/tmp/cosign-out) can hide cosign failures because pipefail isn’t enabled; before
running the pipeline in each "Sign image" step (backend, web, sandbox) enable
pipefail (e.g., run "set -o pipefail" or prefix the shell command to enable
pipefail) so the pipeline returns cosign’s exit code instead of tee’s, ensuring
the if ! ... | tee /tmp/cosign-out check correctly detects failures.
src/synthorg/api/services/org_mutations.py (1)

334-366: ⚠️ Potential issue | 🟠 Major

Don't hardcode saved_by="api" for department-driven snapshots.

These writes now populate company_versions, but every department mutation records the actor as "api". That drops the authenticated user from the audit trail even though the controllers already have the request and update_company() threads through a real saved_by value. Please add a saved_by parameter to these methods and pass it through to _snapshot_company().

Suggested direction
 async def create_department(
     self,
     data: CreateDepartmentRequest,
+    *,
+    saved_by: str = "api",
 ) -> Department:
@@
-            await self._snapshot_company(saved_by="api")
+            await self._snapshot_company(saved_by=saved_by)

 async def update_department(
     self,
     name: str,
     data: UpdateDepartmentRequest,
     *,
     if_match: str | None = None,
+    saved_by: str = "api",
 ) -> Department:
@@
-            await self._snapshot_company(saved_by="api")
+            await self._snapshot_company(saved_by=saved_by)

Apply the same pattern to delete_department() and reorder_departments(), then have the controllers pass the authenticated user ID the same way update_company() already does.

Also applies to: 374-425, 433-503

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/services/org_mutations.py` around lines 334 - 366, The
department mutation methods (create_department, delete_department,
reorder_departments) currently call _snapshot_company(saved_by="api") and must
accept a saved_by parameter so the authenticated user is recorded; update each
method signature to add saved_by: str (or Optional[str]) and propagate that
value into the call to _snapshot_company(saved_by=saved_by), and ensure
callers/controllers that invoke these methods pass the authenticated user ID
(same pattern used by update_company); update any internal calls to
_write_departments/_read_departments/_check_budget_sum as needed to thread the
new saved_by through without changing behavior otherwise.
♻️ Duplicate comments (1)
src/synthorg/api/services/org_mutations.py (1)

111-123: ⚠️ Potential issue | 🟠 Major

Catch ValueError here too.

ConfigResolver.get_budget_config() also documents ValueError for parsing/threshold failures. Since update_company() calls this after persisting the setting write, that uncaught path turns a documented best-effort snapshot into a post-write failure.

Suggested fix
-        except PersistenceError, SettingNotFoundError:
+        except PersistenceError, SettingNotFoundError, ValueError:
             logger.exception(
                 VERSION_SNAPSHOT_FAILED,
                 entity_type="BudgetConfig",
                 entity_id="default",
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/services/org_mutations.py` around lines 111 - 123, The
except block in update_company's budget snapshot currently only catches
PersistenceError and SettingNotFoundError; include ValueError as well so
parsing/threshold failures from ConfigResolver.get_budget_config() are handled
and don't bubble up; change the handler to catch (PersistenceError,
SettingNotFoundError, ValueError) around the snapshot/get_budget_config() call
and keep the existing logger.exception(VERSION_SNAPSHOT_FAILED,
entity_type="BudgetConfig", entity_id="default") behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/api/controllers/workflows.py`:
- Around line 626-629: The call to
version_repo.delete_versions_for_entity(workflow_id) can raise PersistenceError
and should not cause the whole request to fail after the primary workflow was
deleted; wrap that call in a try/except catching PersistenceError (import if
needed), log a warning via the existing logger (e.g., state.app_state.logger or
process logger) that includes workflow_id and the exception, and continue
returning success—ensure the except block does not re-raise so the successful
primary delete is not masked.
- Around line 76-91: The module-level cache _WF_VERSIONING_CACHE used by
_wf_versioning(state: State) is fragile and unbounded; replace it by attaching
the VersioningService directly to state.app_state so the service lifetime
follows the app. In _wf_versioning, read repo =
state.app_state.persistence.workflow_versions and if state.app_state does not
yet have a _wf_versioning_svc attribute, create
state.app_state._wf_versioning_svc = VersioningService(repo) and return that; do
not use id(repo) or the module dict so you avoid stale services and unbounded
growth.

In `@src/synthorg/persistence/sqlite/migrations.py`:
- Around line 83-93: The docstring for the legacy-schema check that mentions
"Warn if workflow_definition_versions has the legacy schema" is outdated; update
it to state that the function logs an error and raises MigrationError when it
detects the legacy layout of workflow_definition_versions (pre-v0.7 flattened
columns like definition_id/name/workflow_type) instead of the VersionSnapshot
format (entity_id/content_hash/snapshot), and include guidance that operators
must migrate data before access because the check will now fail-fast by raising
MigrationError.
- Around line 94-98: The PRAGMA query block starting at "cursor = await
db.execute(...)" can raise SQLite errors that bypass migration logging; wrap the
db.execute/fetchall call in a try/except that catches database errors
(sqlite3.Error or the DB client's exception) around the "cursor = await
db.execute(...)" and "await cursor.fetchall()" operations, log the error at
WARNING/ERROR with context (including the PRAGMA and migration name/version) and
re-raise a MigrationError (use the existing MigrationError class) so callers
only see MigrationError; update references to "cursor" and "columns = {row[1]
for row in await cursor.fetchall()}" to occur inside the try block.

---

Outside diff comments:
In @.github/workflows/docker.yml:
- Around line 225-237: The cosign signing pipeline (the command using DIGEST,
cosign sign and piping to tee writing /tmp/cosign-out) can hide cosign failures
because pipefail isn’t enabled; before running the pipeline in each "Sign image"
step (backend, web, sandbox) enable pipefail (e.g., run "set -o pipefail" or
prefix the shell command to enable pipefail) so the pipeline returns cosign’s
exit code instead of tee’s, ensuring the if ! ... | tee /tmp/cosign-out check
correctly detects failures.

In `@src/synthorg/api/services/org_mutations.py`:
- Around line 334-366: The department mutation methods (create_department,
delete_department, reorder_departments) currently call
_snapshot_company(saved_by="api") and must accept a saved_by parameter so the
authenticated user is recorded; update each method signature to add saved_by:
str (or Optional[str]) and propagate that value into the call to
_snapshot_company(saved_by=saved_by), and ensure callers/controllers that invoke
these methods pass the authenticated user ID (same pattern used by
update_company); update any internal calls to
_write_departments/_read_departments/_check_budget_sum as needed to thread the
new saved_by through without changing behavior otherwise.

---

Duplicate comments:
In `@src/synthorg/api/services/org_mutations.py`:
- Around line 111-123: The except block in update_company's budget snapshot
currently only catches PersistenceError and SettingNotFoundError; include
ValueError as well so parsing/threshold failures from
ConfigResolver.get_budget_config() are handled and don't bubble up; change the
handler to catch (PersistenceError, SettingNotFoundError, ValueError) around the
snapshot/get_budget_config() call and keep the existing
logger.exception(VERSION_SNAPSHOT_FAILED, entity_type="BudgetConfig",
entity_id="default") behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f9cc5767-5c4b-4467-89e8-4f6736fb551f

📥 Commits

Reviewing files that changed from the base of the PR and between b04e59a and 37df199.

⛔ Files ignored due to path filters (1)
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/state.py
  • src/synthorg/persistence/sqlite/migrations.py
  • tests/unit/api/fakes_workflow.py
  • web/src/api/types.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Dashboard Test
  • GitHub Check: Test (Python 3.14)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

No from __future__ import annotations—Python 3.14 has PEP 649

Use except A, B: (no parentheses) for exception syntax—PEP 758, enforced by ruff on Python 3.14

Type hints: all public functions, mypy strict mode

Docstrings: Google style, required on public classes/functions (enforced by ruff D rules)

Create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.

For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Config vs runtime state: frozen Pydantic models for config/identity; separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time.

Use @computed_field for derived values instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens)

Use NotBlankStr (from core.types) for all identifier/name fields—including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants—instead of manual whitespace validators.

Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.

Line length: 88 characters (ruff)

Functions: < 50 lines, files < 800 lines

Handle errors explicitly, never silently swallow

Validate at system boundaries (user input, external APIs, config files)

Variable name: always logger (not _logger, not log)

Python: 3.14+ (PEP 6...

Files:

  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/persistence/sqlite/migrations.py
  • src/synthorg/api/services/org_mutations.py
  • tests/unit/api/fakes_workflow.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)

Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, observability/http_handler.py, and observability/otlp_handler.py may use stdlib logging and print(..., file=sys.stderr) for handler construction, bootstrap, and error reporting.

Event names: always use constants from domain-specific modules under synthorg.observability.events. Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT

Structured kwargs: always logger.info(EVENT, key=value)—never logger.info("msg %s", val)

All error paths must log at WARNING or ERROR with context before raising

All state transitions must log at INFO

DEBUG logging for object creation, internal flow, entry/exit of key functions

All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code—it's handled by the base class.

RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig

Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately without retry.

RetryExhaustedError signals that all retries failed—the engine layer catches this to trigger fallback chains

Rate limiter respects RateLimitError.retry_after from providers—automatically pauses future requests

Vendor-agnostic everywhere: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases.

Files:

  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/persistence/sqlite/migrations.py
  • src/synthorg/api/services/org_mutations.py
src/**/*.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/persistence/sqlite/migrations.py
  • src/synthorg/api/services/org_mutations.py
web/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (web/CLAUDE.md)

web/src/**/*.{ts,tsx,js,jsx}: Always use createLogger from @/lib/logger -- never bare console.warn/console.error/console.debug in application code
Logger variable name must always be const log (e.g. const log = createLogger('module-name'))
Pass dynamic/untrusted values as separate arguments to logger methods (not interpolated into the message string) so they go through sanitizeArg
Attacker-controlled fields inside structured objects must be wrapped in sanitizeForLog() before embedding in log calls

Files:

  • web/src/api/types.ts
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (web/CLAUDE.md)

web/src/**/*.{ts,tsx}: Use Tailwind semantic classes (text-foreground, bg-card, text-accent, text-success, bg-danger, etc.) or CSS variables (var(--so-*)) for colors; NEVER hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (Geist tokens) for typography; NEVER set fontFamily directly in .tsx/.ts files
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; NEVER hardcode pixel values for layout spacing in components
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows and borders; NEVER hardcode values in .tsx/.ts files
Use @/lib/motion presets for Framer Motion transition durations; NEVER hardcode transition durations
CSS side-effect imports in TypeScript 6 require type declarations -- add /// <reference types="vite/client" /> at the top of files with CSS imports

Web Dashboard Design System (MANDATORY): See web/CLAUDE.md for full component inventory. ALWAYS reuse existing components from web/src/components/ui/ before creating new ones.

NEVER hardcode hex colors, font-family, pixel spacing, or Framer Motion transitions in web code—use design tokens and @/lib/motion presets

Files:

  • web/src/api/types.ts
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow

Timeout: 30 seconds per test (global in pyproject.toml—do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed)

Parametrize: Prefer @pytest.mark.parametrize for testing similar cases

Tests must use test-provider, test-small-001, etc. instead of real vendor names

Python uses Hypothesis (@given + @settings) for property-based testing. Hypothesis profiles configured in tests/conftest.py: ci (deterministic, max_examples=10 + derandomize=True), dev (1000 examples), fuzz (10,000 examples, no deadline), extreme (500,000 examples, no deadline).

NEVER skip, dismiss, or ignore flaky tests—always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks that must block indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/api/fakes_workflow.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/api/fakes_workflow.py
🧠 Learnings (33)
📓 Common learnings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 12 design pages.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: When implementation deviates from the design spec, alert the user and explain why—user decides whether to proceed or update the spec. Do NOT silently diverge—every deviation needs explicit user approval.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding starts.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: At every phase of planning and implementation, be critical—actively look for ways to improve the design in the spirit of what we're building (robustness, correctness, simplicity, future-proofing where it's free).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Surface improvements as suggestions, not silent changes—user decides.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Prioritize issues by dependency order, not priority labels—unblocked dependencies come first.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Coverage: 80% minimum (enforced in CI)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Parallelism: `pytest-xdist` via `-n 8`—ALWAYS include `-n 8` when running pytest locally, never run tests sequentially. CI uses `-n auto`.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: When Hypothesis finds a failure, it is a real bug—the shrunk example is saved but is not replayed automatically. Do NOT just rerun and move on. Fix the underlying bug and add an explicit `example(...)` decorator permanently.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Commits: `<type>: <description>`—types: feat, fix, refactor, docs, test, chore, perf, ci. Enforced by commitizen (commit-msg hook)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Signed commits: required on `main` via branch protection—all commits must be GPG/SSH signed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Branches: `<type>/<slug>` from main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Pre-commit hooks: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint + go vet (conditional on cli), no-em-dashes, no-redundant-timeout, eslint-web (conditional on web)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Hookify rules (committed in `.claude/hookify.*.md`): `block-pr-create` (blocks direct `gh pr create`), `enforce-parallel-tests` (enforces `-n 8` with pytest), `no-cd-prefix` (blocks `cd` prefix in Bash commands), `no-local-coverage` (blocks `--cov` flags locally)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Pre-push hooks: mypy type-check (affected modules) + pytest unit tests (affected modules) + golangci-lint + go vet + go test (CLI) + eslint-web (dashboard) (fast gate before push, skipped in CI). Foundational module changes (core, config, observability) or conftest changes trigger full runs.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: GitHub issue queries: use `gh issue list` via Bash (not MCP tools)—MCP `list_issues` has unreliable field data
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Merge strategy: squash merge—PR body becomes the squash commit message on main. Trailers (e.g. `Release-As`, `Closes `#N``) must be in the PR body to land in the final commit.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Preserve existing `Closes `#NNN`` references in PR issue references—never remove unless explicitly asked
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: After finishing an issue implementation: always create a feature branch (`<type>/<slug>`), commit, and push—do NOT create a PR automatically. Do NOT leave work uncommitted on main—branch, commit, push immediately after finishing.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: NEVER create a PR directly—`gh pr create` is blocked by hookify. ALWAYS use `/pre-pr-review` to create PRs—it runs automated checks + review agents + fixes before creating the PR.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: For trivial/docs-only changes: `/pre-pr-review quick` skips agents but still runs automated checks
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: After the PR exists, use `/aurelio-review-pr` to handle external reviewer feedback
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: When review agents find valid issues (including pre-existing issues, suggestions, and findings adjacent to the PR's changes), fix them all. No deferring, no "out of scope" skipping.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: NEVER use `cd` in Bash commands—the working directory is already set to the project root. Use absolute paths or run commands directly. Exception: `bash -c "cd <dir> && <cmd>"` is safe. Use this for tools without a `-C` flag.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: NEVER use Bash to write or modify files—use the Write or Edit tools. Do not use `cat >`, `cat << EOF`, `echo >`, `echo >>`, `sed -i`, `python -c "open(...).write(...)", or `tee` (read-only uses like piping to stdout are fine).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: A PostToolUse hook (`scripts/check_web_design_system.py`) enforces design system rules on every Edit/Write to `web/src/`
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T21:54:57.367Z
Learning: Layout: `src/synthorg/` (src layout), `tests/` (unit/integration/e2e), `web/` (React 19 dashboard), `cli/` (Go CLI binary)
📚 Learning: 2026-03-30T10:20:08.544Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T10:20:08.544Z
Learning: Applies to web/**/*.stories.{ts,tsx} : Storybook 10: Use storybook/test (not storybook/test) and storybook/actions (not storybook/addon-actions) import paths

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-03-15T21:32:02.880Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:32:02.880Z
Learning: Applies to .github/workflows/*.yml : Dependabot: daily updates for uv + github-actions + npm + pre-commit + docker + gomod, grouped minor/patch, no auto-merge. Use `/review-dep-pr` to review Dependabot PRs before merging.

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-03-30T10:41:40.176Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T10:41:40.176Z
Learning: Applies to web/src/**/*.stories.tsx : Storybook 10: import from `storybook/test` (not `storybook/test`), `storybook/actions` (not `storybook/addon-actions`)

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-04-02T12:21:16.739Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T12:21:16.739Z
Learning: Applies to .github/workflows/pages.yml : Pages job: version extraction from `pyproject.toml`, OpenAPI export, Astro + Zensical docs build (with version banner), GitHub Pages deploy on push to main

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-03-15T21:32:02.880Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:32:02.880Z
Learning: Applies to .github/workflows/cli.yml : CLI workflow: Go lint (golangci-lint + go vet) + test (-race -coverprofile) + build (cross-compile: linux/darwin/windows × amd64/arm64) + govulncheck + fuzz testing (main-only, 30s/target, continue-on-error, matrix over 4 packages). cli-pass gate includes fuzz as informational. GoReleaser release on v* tags. Cosign keyless signing of checksums.txt. SLSA L3 provenance attestations. Sigstore bundle (.sigstore.json) attached. Post-release appends checksums/verification/provenance to draft release notes.

Applied to files:

  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
📚 Learning: 2026-03-15T21:32:02.880Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:32:02.880Z
Learning: Applies to .github/workflows/docker.yml : Docker workflow: builds backend + web + sandbox images, pushes to GHCR, signs with cosign. SLSA L3 provenance attestations via actions/attest-build-provenance. Scans: Trivy (CRITICAL = hard fail, HIGH = warn) + Grype (critical cutoff) + CIS Docker Benchmark v1.6.0 compliance (informational). CVE triage via .github/.trivyignore.yaml and .github/.grype.yaml. Images only pushed after scans pass. Triggers on push to main and version tags (v*).

Applied to files:

  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
📚 Learning: 2026-03-19T11:19:40.044Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:19:40.044Z
Learning: CLI workflow (`.github/workflows/cli.yml`) runs Go lint (golangci-lint + go vet) + test (race, coverage) + build (cross-compile matrix) + vulnerability check (govulncheck) + fuzz testing. Cross-compiles for linux/darwin/windows × amd64/arm64. GoReleaser release on v* tags with cosign keyless signing and SLSA L3 attestations.

Applied to files:

  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
📚 Learning: 2026-03-15T12:00:18.113Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T12:00:18.113Z
Learning: Applies to .github/workflows/docker.yml : CI Docker: build → scan → push to GHCR + cosign sign + SLSA L3 provenance via attest-build-provenance (images only pushed after Trivy/Grype scans pass).

Applied to files:

  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
📚 Learning: 2026-04-02T08:47:46.313Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:47:46.313Z
Learning: Pre-push hooks enforce: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional) + eslint-web (web dashboard, conditional) — fast gate before push, skipped in pre-commit.ci

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-03-21T14:12:17.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T14:12:17.848Z
Learning: Docker workflow (`docker.yml`): build + Trivy/Grype scan + push to GHCR + cosign sign + SLSA L3 provenance. CVE triage: `.github/.trivyignore.yaml`, `.github/.grype.yaml`

Applied to files:

  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
📚 Learning: 2026-03-31T14:28:28.895Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:28:28.895Z
Learning: Applies to docker/**/Dockerfile* : Docker images: backend (Chainguard distroless, non-root), web (nginx-unprivileged, SPA + API proxy), sandbox (Python + Node.js, non-root)

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Dependabot: auto-updates Docker image digests and versions daily.

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-21T14:12:17.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T14:12:17.848Z
Learning: Applies to Dockerfile* : Backend images must use Chainguard distroless with non-root user; web image must use nginx-unprivileged with non-root user; sandbox image must use Python + Node.js with non-root user. All Docker files in `docker/` -- Dockerfiles, compose, `.env.example`. Single root `.dockerignore`. All images build with `context: .`

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to docker/{Dockerfile*,compose.yml} : Docker: Backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web uses nginxinc/nginx-unprivileged, Vue 3 SPA with PrimeVue + Tailwind CSS, SPA routing, API/WebSocket proxy to backend.

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-15T21:32:02.880Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:32:02.880Z
Learning: Applies to .github/workflows/finalize-release.yml : Finalize Release workflow: publishes draft releases created by Release Please. Triggers on workflow_run completion of Docker and CLI workflows. Verifies both workflows succeeded for the associated tag before publishing. Extracts CLI checksums, cosign verification, and container verification data from HTML comments, assembles into combined Verification section. Guards against PR-triggered runs. Handles TOCTOU races. Immutable releases enabled—once published, release assets and body cannot be modified.

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: CLI verifies cosign signatures + SLSA provenance at pull time; bypass with `--skip-verify`

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-21T14:12:17.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T14:12:17.848Z
Learning: CLI workflow (`cli.yml`): Go lint/test/build (cross-compile) + govulncheck + fuzz. GoReleaser release on `v*` tags with cosign signing + SLSA provenance

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docker/Dockerfile.sandbox : Docker sandbox: `synthorg-sandbox` — Python 3.14 + Node.js + git, non-root (UID 10001), agent code execution sandbox

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/api/**/*.py : REST API: Litestar framework, controllers with guards, channels for WebSocket, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint. RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation).

Applied to files:

  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers

Applied to files:

  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-04-08T20:20:05.318Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T20:20:05.318Z
Learning: Applies to src/synthorg/**/*.py : If implementation deviates from the design spec (better approach found, scope evolved, etc.), alert the user and explain why before proceeding; do NOT silently diverge

Applied to files:

  • src/synthorg/persistence/sqlite/migrations.py
  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/**/*.py : `RetryConfig` and `RateLimiterConfig` are set per-provider in `ProviderConfig`. Retryable errors: `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately.

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators

Applied to files:

  • tests/unit/api/fakes_workflow.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • tests/unit/api/fakes_workflow.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • tests/unit/api/fakes_workflow.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators

Applied to files:

  • tests/unit/api/fakes_workflow.py
🔇 Additional comments (11)
web/src/api/types.ts (1)

1782-1803: Versioning type generalization is correctly modeled.

VersionSummary<TSnapshot> and WorkflowDefinitionVersionSummary = VersionSummary<WorkflowDefinitionSnapshot> align with the backend VersionSnapshot[T] envelope and remove duplicated workflow-specific envelope typing.

.github/workflows/ci.yml (1)

329-345: Good reliability improvement for Storybook CI failures.

This is a solid change: Line 329 adds explicit Node crash diagnostics/heap tuning, and Lines 339-345 capture crash reports only on failure without introducing flaky follow-up failures.

tests/unit/api/fakes_workflow.py (5)

9-14: LGTM!

The TYPE_CHECKING imports for NotBlankStr and VersionSnapshot are the correct pattern for Python 3.14's PEP 649 lazy annotations.


113-123: LGTM!

The class correctly adopts the new VersionSnapshot[WorkflowDefinition] model with (entity_id, version) composite keys. The docstring accurately documents protocol conformance via structural typing.


125-133: LGTM!

The save_version implementation correctly returns bool with INSERT OR IGNORE semantics. This is critical for VersioningService.snapshot_if_changed() to detect concurrent-write conflicts and trigger _resolve_conflict().


135-166: LGTM!

The getter methods correctly implement the VersionRepository protocol:

  • get_version uses direct key lookup
  • get_latest_version returns the highest version number for the entity
  • get_by_content_hash returns the newest version among matching hashes (aligning with production ORDER BY version DESC)

All methods now use NotBlankStr type hints and return deep copies.


168-198: LGTM!

The list/count/delete methods are well-implemented:

  • list_versions now validates non-negative pagination parameters, matching production behavior
  • count_versions correctly counts by entity
  • delete_versions_for_entity properly filters composite keys by entity_id
src/synthorg/persistence/sqlite/migrations.py (1)

74-76: Good fail-fast placement for legacy schema detection.

Running the legacy-shape check before PERSISTENCE_MIGRATION_COMPLETED is the right ordering and prevents booting with an incompatible workflow versions table.

src/synthorg/api/state.py (1)

200-215: Good catch wiring company_versions through both initialization paths.

This closes the deferred-settings case too, so OrgMutationService gets the same versioning dependencies whether it is built in __init__() or later in set_settings_service().

Also applies to: 845-858

src/synthorg/api/controllers/workflows.py (2)

439-451: LGTM on snapshot error handling migration.

The refactored code correctly catches PersistenceError as documented by VersioningService.snapshot_if_changed(), logs with logger.exception() (preserving stack trace), and allows the primary operation to succeed even if snapshotting fails. The pattern is consistently applied across all three mutation endpoints.

Also applies to: 526-538, 587-599


14-14: Imports are correctly updated for the refactored versioning pattern.

The removed build_version_snapshot import is replaced by VersioningService, and the exception types align with the new error handling.

Also applies to: 67-75

Comment on lines +76 to +91
_WF_VERSIONING_CACHE: dict[int, VersioningService[WorkflowDefinition]] = {}


def _wf_versioning(state: State) -> VersioningService[WorkflowDefinition]:
"""Return a VersioningService for workflow definitions.

Uses the persistence backend's identity to avoid repeated
allocations across controller methods.
"""
repo = state.app_state.persistence.workflow_versions
key = id(repo)
svc = _WF_VERSIONING_CACHE.get(key)
if svc is None:
svc = VersioningService(repo)
_WF_VERSIONING_CACHE[key] = svc
return svc
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how tests handle app_state/persistence to assess impact
rg -n -A3 'app_state\.persistence' tests/

Repository: Aureliolo/synthorg

Length of output: 23711


🏁 Script executed:

# Find conftest.py to understand test fixtures and app lifecycle
find tests -name "conftest.py" -type f

Repository: Aureliolo/synthorg

Length of output: 1716


🏁 Script executed:

# Search for TestClient instantiation patterns to understand if app is recreated per test
rg -n "TestClient|FastAPI\(\)" tests/ | head -30

Repository: Aureliolo/synthorg

Length of output: 2792


🏁 Script executed:

# Check if the module-level cache persists across test runs
# Look for any test isolation or app recreation patterns
rg -n "app_state|persistence" tests/ -B2 -A2 | grep -E "(fixture|setup|teardown|conftest)" | head -20

Repository: Aureliolo/synthorg

Length of output: 1679


🏁 Script executed:

# Check the main conftest to understand app fixture lifecycle
cat tests/unit/api/conftest.py

Repository: Aureliolo/synthorg

Length of output: 13744


🏁 Script executed:

# Check test_workflows.py conftest specifically
cat tests/unit/api/controllers/conftest.py

Repository: Aureliolo/synthorg

Length of output: 1054


🏁 Script executed:

# Look for fixture scope to understand if app is recreated per test
rg -n "@pytest.fixture" tests/unit/api/conftest.py tests/unit/api/controllers/conftest.py -A1

Repository: Aureliolo/synthorg

Length of output: 2477


🏁 Script executed:

# Check if repos are actually garbage-collected between tests
# by looking at whether app instances have references that keep repos alive
rg -n "with TestClient" tests/unit/api/controllers/test_workflows.py -A5 -B2

Repository: Aureliolo/synthorg

Length of output: 44


🏁 Script executed:

# Check the actual repo object lifecycle by examining FakePersistenceBackend
rg -n "class FakePersistenceBackend" tests/ -A20

Repository: Aureliolo/synthorg

Length of output: 1947


🏁 Script executed:

# Check if test_workflows.py actually uses the _wf_versioning function
# to understand if module cache issues would surface
rg -n "_wf_versioning" tests/unit/api/controllers/test_workflows.py

Repository: Aureliolo/synthorg

Length of output: 44


🏁 Script executed:

# Look at the actual test execution to see if multiple tests create repos
rg -n "def test_" tests/unit/api/controllers/test_workflows.py | head -20

Repository: Aureliolo/synthorg

Length of output: 1584


🏁 Script executed:

# Check if _wf_versioning is actually called in the workflow controller methods
rg -n "_wf_versioning" src/synthorg/api/controllers/workflows.py

Repository: Aureliolo/synthorg

Length of output: 259


🏁 Script executed:

# Look at how the function is used in the actual route handlers
rg -n "def create_workflow\|def update_workflow\|def delete_workflow" src/synthorg/api/controllers/workflows.py -A10 | head -50

Repository: Aureliolo/synthorg

Length of output: 44


🏁 Script executed:

# Check if there are any actual test failures or issues related to _wf_versioning
rg -n "VersioningService" tests/unit/api/controllers/test_workflows.py

Repository: Aureliolo/synthorg

Length of output: 44


🏁 Script executed:

# Look at the actual repository object to understand if id() reuse is a real concern
cat src/synthorg/api/controllers/workflows.py | head -100

Repository: Aureliolo/synthorg

Length of output: 3385


🏁 Script executed:

# Look for any tests that explicitly test version conflict behavior
rg -n "version_conflict\|VersionConflictError" tests/unit/api/controllers/test_workflows.py -B3 -A3

Repository: Aureliolo/synthorg

Length of output: 44


🏁 Script executed:

# Check if the tests actually exercise the _wf_versioning cache across multiple test runs
# by looking at test methods that use snapshot_if_changed
rg -n "snapshot_if_changed" tests/unit/api/controllers/ -l

Repository: Aureliolo/synthorg

Length of output: 44


🏁 Script executed:

# Check integration tests to see if they create multiple app instances
rg -n "TestClient\|create_app" tests/integration/ | head -20

Repository: Aureliolo/synthorg

Length of output: 44


🏁 Script executed:

# Verify if FakePersistenceBackend repo objects would have stable ids across tests
# by checking if the test fixture creates fresh instances per test
rg -n "fake_persistence" tests/unit/api/conftest.py -A5 -B2

Repository: Aureliolo/synthorg

Length of output: 1525


🏁 Script executed:

# Check if this pattern appears in production code or is test-only
rg -n "id\(" src/synthorg/ --type py | rg -v test | head -10

Repository: Aureliolo/synthorg

Length of output: 1010


🏁 Script executed:

# Verify if persistence backend is actually a singleton or could be recreated
rg -n "persistence.*=.*Persistence\|create_persistence" src/synthorg/ --type py -B2 -A2

Repository: Aureliolo/synthorg

Length of output: 44


Module-level cache using id() as key is an architectural anti-pattern; attach the service to state.app_state instead.

The _WF_VERSIONING_CACHE grows unbounded and couples cache ownership to module initialization rather than app lifetime. Using id(repo) is fragile: if the persistence backend is ever recreated during app operation, the cache returns a stale VersioningService bound to the old repo. More importantly, there's no cleanup mechanism—the cache accumulates entries indefinitely.

Attach the service directly to state.app_state alongside the repo to tie lifetimes together and clarify ownership:

def _wf_versioning(state: State) -> VersioningService[WorkflowDefinition]:
    """Return a VersioningService for workflow definitions."""
    if not hasattr(state.app_state, "_wf_versioning_svc"):
        repo = state.app_state.persistence.workflow_versions
        state.app_state._wf_versioning_svc = VersioningService(repo)
    return state.app_state._wf_versioning_svc
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/controllers/workflows.py` around lines 76 - 91, The
module-level cache _WF_VERSIONING_CACHE used by _wf_versioning(state: State) is
fragile and unbounded; replace it by attaching the VersioningService directly to
state.app_state so the service lifetime follows the app. In _wf_versioning, read
repo = state.app_state.persistence.workflow_versions and if state.app_state does
not yet have a _wf_versioning_svc attribute, create
state.app_state._wf_versioning_svc = VersioningService(repo) and return that; do
not use id(repo) or the module dict so you avoid stale services and unbounded
growth.

@Aureliolo Aureliolo force-pushed the feat/versioning-generalization branch from 37df199 to f264d94 Compare April 8, 2026 22:29
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 8, 2026 22:30 — with GitHub Actions Inactive
@Aureliolo
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/synthorg/api/services/org_mutations.py (1)

526-580: ⚠️ Potential issue | 🟡 Minor

Agent mutations do not snapshot the company structure.

The create_agent, update_agent, and delete_agent methods modify the org configuration but do not call _snapshot_company(). This means agent additions, changes, and deletions are not captured in the company version history, unlike department mutations which do trigger company snapshots.

If agents are considered part of the company structure for versioning purposes, consider adding _snapshot_company(saved_by=...) to these methods. If agents are intentionally excluded from company versioning, this is fine but should be documented.

Also applies to: 630-677, 679-714

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/services/org_mutations.py` around lines 526 - 580, The agent
mutations (create_agent, update_agent, delete_agent) currently modify agents but
do not record a company snapshot; update each of these functions to call
self._snapshot_company(saved_by=...) immediately after successfully writing
agents (i.e., after await self._write_agents(...) and before/after releasing the
org lock), passing an appropriate actor identifier (use data.saved_by if the
request carries it or the existing request/user context on the service) so agent
changes are captured in the company version history via _snapshot_company.
src/synthorg/api/controllers/workflow_versions.py (1)

364-376: ⚠️ Potential issue | 🟡 Minor

Snapshot failure is silently swallowed after successful definition save.

If repo.save(rolled_back) succeeds but svc.snapshot_if_changed() fails, the rollback completes and returns 200 with the rolled-back definition, but no version snapshot is persisted. This creates an inconsistency where the workflow state exists but its version history is incomplete.

Consider whether this best-effort behavior is intentional. If version history is critical for auditing rollbacks, this should either:

  1. Be wrapped in a transaction so both succeed or fail together
  2. Return a warning in the response indicating partial success
  3. Be documented explicitly in the endpoint's behavior

If best-effort is acceptable (similar to _snapshot_budget_config in org_mutations.py), the current implementation is fine but could benefit from a comment explaining the trade-off.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/controllers/workflow_versions.py` around lines 364 - 376,
The code currently saves the rolled-back definition (repo.save(rolled_back)) and
then best-effort calls VersioningService.snapshot_if_changed
(svc.snapshot_if_changed) swallowing PersistenceError; to fix, make snapshot
persistence part of the same atomic operation or explicitly surface partial
failure: either (A) wrap repo.save and svc.snapshot_if_changed in a transaction
so both succeed or both rollback (use your DB/ORM transaction helper around the
save + snapshot call and re-raise on failure), or (B) if best-effort is
intended, add a clear comment and change the controller response to include a
warning field when the snapshot step raises PersistenceError (log
WORKFLOW_VERSION_SNAPSHOT_FAILED as now but also append a warning to the HTTP
response payload so callers know version history may be incomplete). Ensure
references to VersioningService, snapshot_if_changed, PersistenceError,
repo.save and WORKFLOW_VERSION_SNAPSHOT_FAILED are updated accordingly.
♻️ Duplicate comments (1)
src/synthorg/engine/workflow/diff.py (1)

195-206: ⚠️ Potential issue | 🟠 Major

Validate each snapshot against its own entity_id, not just against the other snapshot.

Line 195 only rejects differing snapshot.id values. If both persisted payloads carry the same wrong snapshot.id, compute_diff() still returns a diff for definition_id=old.entity_id, which can attribute changes to the wrong workflow. Check old.snapshot.id == old.entity_id and new.snapshot.id == new.entity_id before diffing.

🛡️ Proposed fix
-    if old.snapshot.id != new.snapshot.id:
+    if (
+        old.snapshot.id != old.entity_id
+        or new.snapshot.id != new.entity_id
+        or old.snapshot.id != new.snapshot.id
+    ):
         msg = (
-            "Snapshot definition IDs do not match entity_id "
-            f"(old={old.snapshot.id!r}, new={new.snapshot.id!r})"
+            "Version snapshot payload/entity mismatch "
+            f"(old_entity_id={old.entity_id!r}, old_snapshot_id={old.snapshot.id!r}, "
+            f"new_entity_id={new.entity_id!r}, new_snapshot_id={new.snapshot.id!r})"
         )
         logger.warning(
             WORKFLOW_DEF_INVALID_REQUEST,
+            old_definition_id=old.entity_id,
+            new_definition_id=new.entity_id,
             old_snapshot_id=old.snapshot.id,
             new_snapshot_id=new.snapshot.id,
             reason=msg,
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/workflow/diff.py` around lines 195 - 206, The current
check only compares old.snapshot.id vs new.snapshot.id; instead validate each
snapshot matches its declared entity id before diffing by asserting
old.snapshot.id == old.entity_id and new.snapshot.id == new.entity_id (e.g., in
the compute_diff flow surrounding the existing block), logging
WORKFLOW_DEF_INVALID_REQUEST with old_snapshot_id/new_snapshot_id and a clear
reason for each mismatch and raising ValueError when either snapshot.id does not
equal its entity_id; ensure these checks run prior to any diff computation that
uses definition_id=old.entity_id.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/docker.yml:
- Around line 230-238: The Rekor conflict branch currently skips on
'createLogEntryConflict' without confirming the image is actually signed; update
the conflict-handling branch that greps for 'createLogEntryConflict' (the block
around the cosign sign invocation and /tmp/cosign-out) to run a cosign verify
for the same image digest (${DIGEST}) before treating it as success: call cosign
verify with certificate identity/issuer constraints (e.g. --certificate-identity
"<OIDC_SUBJECT>" --certificate-issuer "<OIDC_ISSUER>") against
ghcr.io/aureliolo/synthorg-backend@${DIGEST} and fail the step if verify returns
non-zero, otherwise print a notice and continue; apply this same change to the
other two sign steps that use the same pattern (grep 'createLogEntryConflict' on
/tmp/cosign-out).

---

Outside diff comments:
In `@src/synthorg/api/controllers/workflow_versions.py`:
- Around line 364-376: The code currently saves the rolled-back definition
(repo.save(rolled_back)) and then best-effort calls
VersioningService.snapshot_if_changed (svc.snapshot_if_changed) swallowing
PersistenceError; to fix, make snapshot persistence part of the same atomic
operation or explicitly surface partial failure: either (A) wrap repo.save and
svc.snapshot_if_changed in a transaction so both succeed or both rollback (use
your DB/ORM transaction helper around the save + snapshot call and re-raise on
failure), or (B) if best-effort is intended, add a clear comment and change the
controller response to include a warning field when the snapshot step raises
PersistenceError (log WORKFLOW_VERSION_SNAPSHOT_FAILED as now but also append a
warning to the HTTP response payload so callers know version history may be
incomplete). Ensure references to VersioningService, snapshot_if_changed,
PersistenceError, repo.save and WORKFLOW_VERSION_SNAPSHOT_FAILED are updated
accordingly.

In `@src/synthorg/api/services/org_mutations.py`:
- Around line 526-580: The agent mutations (create_agent, update_agent,
delete_agent) currently modify agents but do not record a company snapshot;
update each of these functions to call self._snapshot_company(saved_by=...)
immediately after successfully writing agents (i.e., after await
self._write_agents(...) and before/after releasing the org lock), passing an
appropriate actor identifier (use data.saved_by if the request carries it or the
existing request/user context on the service) so agent changes are captured in
the company version history via _snapshot_company.

---

Duplicate comments:
In `@src/synthorg/engine/workflow/diff.py`:
- Around line 195-206: The current check only compares old.snapshot.id vs
new.snapshot.id; instead validate each snapshot matches its declared entity id
before diffing by asserting old.snapshot.id == old.entity_id and new.snapshot.id
== new.entity_id (e.g., in the compute_diff flow surrounding the existing
block), logging WORKFLOW_DEF_INVALID_REQUEST with
old_snapshot_id/new_snapshot_id and a clear reason for each mismatch and raising
ValueError when either snapshot.id does not equal its entity_id; ensure these
checks run prior to any diff computation that uses definition_id=old.entity_id.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1d77978d-290d-41a7-a0b1-f1a55f3b0ee9

📥 Commits

Reviewing files that changed from the base of the PR and between 37df199 and f264d94.

⛔ Files ignored due to path filters (1)
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (38)
  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
  • CLAUDE.md
  • docs/design/engine.md
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/_workflow_helpers.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/departments.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/services/org_mutations.py
  • src/synthorg/api/state.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/engine/workflow/version.py
  • src/synthorg/observability/events/versioning.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/persistence/sqlite/migrations.py
  • src/synthorg/persistence/sqlite/schema.sql
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/persistence/sqlite/workflow_version_repo.py
  • src/synthorg/persistence/workflow_version_repo.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/api/fakes_backend.py
  • tests/unit/api/fakes_workflow.py
  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/engine/workflow/test_version.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/persistence/sqlite/test_workflow_version_repo.py
  • tests/unit/persistence/test_protocol.py
  • web/src/api/types.ts
  • web/src/pages/workflow-editor/VersionHistoryPanel.tsx
💤 Files with no reviewable changes (6)
  • src/synthorg/api/controllers/_workflow_helpers.py
  • tests/unit/engine/workflow/test_version.py
  • src/synthorg/engine/workflow/version.py
  • tests/unit/persistence/sqlite/test_workflow_version_repo.py
  • src/synthorg/persistence/workflow_version_repo.py
  • src/synthorg/persistence/sqlite/workflow_version_repo.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (7)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Always read the relevant docs/design/ page before implementing any feature or planning any issue. The design spec is the starting point for architecture, data models, and behavior

If implementation deviates from the spec (better approach found, scope evolved, etc.), alert the user and explain why before proceeding. Do NOT silently diverge -- every deviation needs explicit user approval

Every implementation plan must be presented to the user for accept/deny before coding starts

Line length: 88 characters (enforced by ruff)

Files:

  • src/synthorg/observability/events/versioning.py
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/departments.py
  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • tests/unit/api/fakes_backend.py
  • src/synthorg/engine/workflow/diff.py
  • tests/unit/persistence/test_protocol.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/migrations.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • src/synthorg/api/services/org_mutations.py
  • tests/unit/api/fakes_workflow.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Do NOT import from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations

Use PEP 758 except syntax: use except A, B: (no parentheses) -- ruff enforces this on Python 3.14

Type hints required on all public functions, enforced with mypy strict mode

Docstrings must use Google style format and are required on all public classes and functions (enforced by ruff D rules)

Create new objects for immutability, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement

For dict/list fields in frozen Pydantic models, use frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)

Use frozen Pydantic models for config and identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves (e.g. agent execution state, task progress). Never mix static config fields with mutable runtime fields in one model

Use Pydantic v2 with: BaseModel, model_validator, computed_field, ConfigDict. Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time

Use @computed_field for derived values instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens)

Use NotBlankStr from core.types for all identifier/name fields -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators

Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task

Keep functions under 50 lines and files under 800 lines

Handle errors explicitly, never silently swallow exceptions

Validate at system...

Files:

  • src/synthorg/observability/events/versioning.py
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/departments.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/migrations.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/services/org_mutations.py
**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Never use cd in Bash commands -- the working directory is already set to the project root. Use absolute paths or run commands directly. Exception: bash -c "cd <dir> && <cmd>" is safe (runs in a child process). Use this for tools without a -C flag

Never use Bash to write or modify files -- use the Write or Edit tools instead. Do not use cat >, cat << EOF, echo >, echo >>, sed -i, python -c "open(...).write(...)", or tee` to create or modify files

Files:

  • src/synthorg/observability/events/versioning.py
  • web/src/pages/workflow-editor/VersionHistoryPanel.tsx
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/departments.py
  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • docs/design/engine.md
  • src/synthorg/api/controllers/workflows.py
  • tests/unit/api/fakes_backend.py
  • src/synthorg/engine/workflow/diff.py
  • tests/unit/persistence/test_protocol.py
  • CLAUDE.md
  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/migrations.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • web/src/api/types.ts
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • src/synthorg/api/services/org_mutations.py
  • tests/unit/api/fakes_workflow.py
  • src/synthorg/persistence/sqlite/schema.sql
src/**/*.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/observability/events/versioning.py
  • src/synthorg/persistence/sqlite/version_repo.py
  • src/synthorg/api/state.py
  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/departments.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/engine/workflow/diff.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/migrations.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/services/org_mutations.py
web/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (web/CLAUDE.md)

web/src/**/*.{ts,tsx,js,jsx}: Always use createLogger from @/lib/logger -- never bare console.warn/console.error/console.debug in application code
Logger variable name must always be const log (e.g. const log = createLogger('module-name'))
Pass dynamic/untrusted values as separate arguments to logger methods (not interpolated into the message string) so they go through sanitizeArg
Attacker-controlled fields inside structured objects must be wrapped in sanitizeForLog() before embedding in log calls

Files:

  • web/src/pages/workflow-editor/VersionHistoryPanel.tsx
  • web/src/api/types.ts
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (web/CLAUDE.md)

web/src/**/*.{ts,tsx}: Use Tailwind semantic classes (text-foreground, bg-card, text-accent, text-success, bg-danger, etc.) or CSS variables (var(--so-*)) for colors; NEVER hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (Geist tokens) for typography; NEVER set fontFamily directly in .tsx/.ts files
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; NEVER hardcode pixel values for layout spacing in components
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows and borders; NEVER hardcode values in .tsx/.ts files
Use @/lib/motion presets for Framer Motion transition durations; NEVER hardcode transition durations
CSS side-effect imports in TypeScript 6 require type declarations -- add /// <reference types="vite/client" /> at the top of files with CSS imports

Always reuse existing components from web/src/components/ui/ before creating new ones

Never hardcode hex colors, font-family, pixel spacing, or Framer Motion transitions -- use design tokens and @/lib/motion presets

Files:

  • web/src/pages/workflow-editor/VersionHistoryPanel.tsx
  • web/src/api/types.ts
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Mark tests with: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, or @pytest.mark.slow

Coverage minimum 80% (enforced in CI)

Use asyncio_mode = "auto" -- no manual @pytest.mark.asyncio needed

Global timeout: 30 seconds per test (set in pyproject.toml). Do not add per-file pytest.mark.timeout(30) markers; only use non-default overrides like timeout(60) when explicitly needed

Prefer @pytest.mark.parametrize for testing similar cases

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases. Tests must use test-provider, test-small-001, etc.

When Hypothesis finds a failure, it is a real bug. Add an explicit @example(...) decorator to permanently cover the case in CI. Do NOT just rerun and move on

Never skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks that must block indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number)

Always include -n 8 when running pytest locally, never run tests sequentially. CI uses -n auto. This is enforced by hookify enforce-parallel-tests rule

Files:

  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/api/fakes_backend.py
  • tests/unit/persistence/test_protocol.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/api/fakes_workflow.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/engine/workflow/test_diff.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/api/fakes_backend.py
  • tests/unit/persistence/test_protocol.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/unit/api/controllers/test_config_version_controllers.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
  • tests/unit/api/fakes_workflow.py
🧠 Learnings (64)
📓 Common learnings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:41:30.687Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding starts. At every phase of planning and implementation, be critical and actively look for ways to improve the design
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:41:30.687Z
Learning: When approved deviations from the spec occur, update the relevant `docs/design/` page to reflect the new reality
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:41:30.687Z
Learning: Commits must follow format: `<type>: <description>` with types: feat, fix, refactor, docs, test, chore, perf, ci. Enforced by commitizen (commit-msg hook)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:41:30.687Z
Learning: Signed commits required on `main` via branch protection -- all commits must be GPG/SSH signed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:41:30.687Z
Learning: Use branches named `<type>/<slug>` from main (e.g., `feat/add-agent-routing`)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:41:30.687Z
Learning: Never create PRs directly with `gh pr create` -- use `/pre-pr-review` to create PRs. For trivial/docs-only changes, use `/pre-pr-review quick`. This is enforced by hookify block-pr-create rule
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:41:30.687Z
Learning: After finishing implementation, always create a feature branch (`<type>/<slug>`), commit, and push -- do NOT create a PR automatically or leave work uncommitted on main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:41:30.687Z
Learning: When review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to changes), fix them all. No deferring, no 'out of scope' skipping
📚 Learning: 2026-03-30T10:20:08.544Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T10:20:08.544Z
Learning: Applies to web/**/*.stories.{ts,tsx} : Storybook 10: Use storybook/test (not storybook/test) and storybook/actions (not storybook/addon-actions) import paths

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-03-15T21:32:02.880Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:32:02.880Z
Learning: Applies to .github/workflows/*.yml : Dependabot: daily updates for uv + github-actions + npm + pre-commit + docker + gomod, grouped minor/patch, no auto-merge. Use `/review-dep-pr` to review Dependabot PRs before merging.

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-03-15T21:32:02.880Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:32:02.880Z
Learning: Applies to .github/workflows/cli.yml : CLI workflow: Go lint (golangci-lint + go vet) + test (-race -coverprofile) + build (cross-compile: linux/darwin/windows × amd64/arm64) + govulncheck + fuzz testing (main-only, 30s/target, continue-on-error, matrix over 4 packages). cli-pass gate includes fuzz as informational. GoReleaser release on v* tags. Cosign keyless signing of checksums.txt. SLSA L3 provenance attestations. Sigstore bundle (.sigstore.json) attached. Post-release appends checksums/verification/provenance to draft release notes.

Applied to files:

  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
📚 Learning: 2026-03-15T21:32:02.880Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:32:02.880Z
Learning: Applies to .github/workflows/docker.yml : Docker workflow: builds backend + web + sandbox images, pushes to GHCR, signs with cosign. SLSA L3 provenance attestations via actions/attest-build-provenance. Scans: Trivy (CRITICAL = hard fail, HIGH = warn) + Grype (critical cutoff) + CIS Docker Benchmark v1.6.0 compliance (informational). CVE triage via .github/.trivyignore.yaml and .github/.grype.yaml. Images only pushed after scans pass. Triggers on push to main and version tags (v*).

Applied to files:

  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
📚 Learning: 2026-04-02T12:21:16.739Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T12:21:16.739Z
Learning: Applies to .github/workflows/pages.yml : Pages job: version extraction from `pyproject.toml`, OpenAPI export, Astro + Zensical docs build (with version banner), GitHub Pages deploy on push to main

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-03-19T11:19:40.044Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:19:40.044Z
Learning: CLI workflow (`.github/workflows/cli.yml`) runs Go lint (golangci-lint + go vet) + test (race, coverage) + build (cross-compile matrix) + vulnerability check (govulncheck) + fuzz testing. Cross-compiles for linux/darwin/windows × amd64/arm64. GoReleaser release on v* tags with cosign keyless signing and SLSA L3 attestations.

Applied to files:

  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
📚 Learning: 2026-03-15T12:00:18.113Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T12:00:18.113Z
Learning: Applies to .github/workflows/docker.yml : CI Docker: build → scan → push to GHCR + cosign sign + SLSA L3 provenance via attest-build-provenance (images only pushed after Trivy/Grype scans pass).

Applied to files:

  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
📚 Learning: 2026-04-02T08:47:46.313Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:47:46.313Z
Learning: Pre-push hooks enforce: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional) + eslint-web (web dashboard, conditional) — fast gate before push, skipped in pre-commit.ci

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-03-21T14:12:17.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T14:12:17.848Z
Learning: Docker workflow (`docker.yml`): build + Trivy/Grype scan + push to GHCR + cosign sign + SLSA L3 provenance. CVE triage: `.github/.trivyignore.yaml`, `.github/.grype.yaml`

Applied to files:

  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
📚 Learning: 2026-03-30T10:41:40.176Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T10:41:40.176Z
Learning: Applies to web/src/**/*.stories.tsx : Storybook 10: import from `storybook/test` (not `storybook/test`), `storybook/actions` (not `storybook/addon-actions`)

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/api/state.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers

Applied to files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/departments.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/api/**/*.py : REST API: Litestar framework, controllers with guards, channels for WebSocket, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint. RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation).

Applied to files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/api/controllers/departments.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/api/controllers/company.py
  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-31T14:28:28.895Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:28:28.895Z
Learning: Applies to docker/**/Dockerfile* : Docker images: backend (Chainguard distroless, non-root), web (nginx-unprivileged, SPA + API proxy), sandbox (Python + Node.js, non-root)

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-21T14:12:17.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T14:12:17.848Z
Learning: Applies to Dockerfile* : Backend images must use Chainguard distroless with non-root user; web image must use nginx-unprivileged with non-root user; sandbox image must use Python + Node.js with non-root user. All Docker files in `docker/` -- Dockerfiles, compose, `.env.example`. Single root `.dockerignore`. All images build with `context: .`

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-15T21:32:02.880Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:32:02.880Z
Learning: Applies to .github/workflows/finalize-release.yml : Finalize Release workflow: publishes draft releases created by Release Please. Triggers on workflow_run completion of Docker and CLI workflows. Verifies both workflows succeeded for the associated tag before publishing. Extracts CLI checksums, cosign verification, and container verification data from HTML comments, assembles into combined Verification section. Guards against PR-triggered runs. Handles TOCTOU races. Immutable releases enabled—once published, release assets and body cannot be modified.

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Dependabot: auto-updates Docker image digests and versions daily.

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: CLI verifies cosign signatures + SLSA provenance at pull time; bypass with `--skip-verify`

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-21T14:12:17.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T14:12:17.848Z
Learning: CLI workflow (`cli.yml`): Go lint/test/build (cross-compile) + govulncheck + fuzz. GoReleaser release on `v*` tags with cosign signing + SLSA provenance

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docker/Dockerfile.sandbox : Docker sandbox: `synthorg-sandbox` — Python 3.14 + Node.js + git, non-root (UID 10001), agent code execution sandbox

Applied to files:

  • .github/workflows/docker.yml
📚 Learning: 2026-03-30T10:41:40.176Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T10:41:40.176Z
Learning: Update the relevant `docs/design/` page to reflect new reality when approved deviations from spec occur

Applied to files:

  • docs/design/engine.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.

Applied to files:

  • docs/design/engine.md
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: Applies to docs/design/*.md : Update the relevant `docs/design/` page when approved deviations occur to reflect the new reality

Applied to files:

  • docs/design/engine.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/engine.md
📚 Learning: 2026-04-06T16:35:12.934Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-06T16:35:12.934Z
Learning: All project conventions, commands, and standards are defined in CLAUDE.md - refer to it for project structure, package layout, code conventions, quick commands, git workflow, testing standards, design specifications, logging, resilience, and security patterns

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-04-02T12:07:44.443Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T12:07:44.443Z
Learning: Applies to src/synthorg/**/*.py : Always use structured logging: `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Use structured logging: always `logger.info(EVENT, key=value)` — never `logger.info("msg %s", val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/backend.py
  • tests/unit/persistence/sqlite/test_entity_version_repos.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/persistence/protocol.py
  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/memory/**/*.py : Use MemoryBackend protocol with pluggable backends (Mem0 adapter available at backends/mem0/) for persistent agent memory

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/persistence/protocol.py
  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)

Applied to files:

  • src/synthorg/persistence/protocol.py
📚 Learning: 2026-04-08T20:20:05.318Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T20:20:05.318Z
Learning: Applies to src/synthorg/**/*.py : If implementation deviates from the design spec (better approach found, scope evolved, etc.), alert the user and explain why before proceeding; do NOT silently diverge

Applied to files:

  • src/synthorg/persistence/sqlite/migrations.py
  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to {pyproject.toml,src/synthorg/__init__.py} : Update version in `pyproject.toml` (`[tool.commitizen].version`) and `src/synthorg/__init__.py` (`__version__`)

Applied to files:

  • src/synthorg/api/controllers/__init__.py
  • src/synthorg/api/controllers/workflow_versions.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.

Applied to files:

  • src/synthorg/api/controllers/__init__.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/persistence/sqlite/backend.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/synthorg/api/**/*.py : Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Applied to files:

  • src/synthorg/api/controllers/workflow_versions.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/api/**/*.py : Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.

Applied to files:

  • src/synthorg/api/controllers/workflow_versions.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : All state transitions must log at INFO.

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use DEBUG logging for object creation, internal flow, and entry/exit of key functions.

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : All state transitions must log at INFO level

Applied to files:

  • src/synthorg/api/controllers/budget_config_versions.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/**/*.py : `RetryConfig` and `RateLimiterConfig` are set per-provider in `ProviderConfig`. Retryable errors: `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately.

Applied to files:

  • src/synthorg/api/services/org_mutations.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators

Applied to files:

  • tests/unit/api/fakes_workflow.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • tests/unit/api/fakes_workflow.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • tests/unit/api/fakes_workflow.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators

Applied to files:

  • tests/unit/api/fakes_workflow.py

Comment on lines +230 to +238
set -o pipefail
if ! cosign sign --yes ghcr.io/aureliolo/synthorg-backend@${DIGEST} 2>&1 | tee /tmp/cosign-out; then
if grep -q 'createLogEntryConflict' /tmp/cosign-out; then
echo "::notice::Image already signed in transparency log -- skipping"
else
cat /tmp/cosign-out
exit 1
fi
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify each "Sign image" block suppresses createLogEntryConflict
# and whether it performs a cosign verify before continuing.
python - <<'PY'
from pathlib import Path
import re

text = Path(".github/workflows/docker.yml").read_text()

blocks = re.finditer(
    r'- name: Sign image.*?\n\s+run: \|\n(.*?)(?=\n\s+- name: |\n\s*$)',
    text,
    flags=re.S
)

for i, m in enumerate(blocks, 1):
    b = m.group(1)
    print(f"Block {i}:")
    print("  has createLogEntryConflict handling:", "createLogEntryConflict" in b)
    print("  has cosign verify:", "cosign verify" in b)
    print()
PY

Repository: Aureliolo/synthorg

Length of output: 304


Add signature verification to the Rekor conflict-handling branch.

All three "Sign image" steps (backend, web, sandbox) suppress cosign sign failures when Rekor reports a createLogEntryConflict. However, the conflict branch does not verify that the digest is actually signed. A failed signing attempt could pass silently. Add cosign verify with OIDC identity and issuer constraints before continuing.

🔧 Suggested hardening pattern (apply to all three sign steps)
           set -o pipefail
-          if ! cosign sign --yes ghcr.io/aureliolo/synthorg-backend@${DIGEST} 2>&1 | tee /tmp/cosign-out; then
-            if grep -q 'createLogEntryConflict' /tmp/cosign-out; then
-              echo "::notice::Image already signed in transparency log -- skipping"
+          COSIGN_OUT="$(mktemp)"
+          trap 'rm -f "$COSIGN_OUT"' EXIT
+          if ! cosign sign --yes ghcr.io/aureliolo/synthorg-backend@${DIGEST} 2>&1 | tee "$COSIGN_OUT"; then
+            if grep -q 'createLogEntryConflict' "$COSIGN_OUT"; then
+              echo "::notice::Rekor conflict detected; verifying existing signature"
+              cosign verify \
+                --certificate-identity-regexp='github\.com/Aureliolo/synthorg' \
+                --certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
+                ghcr.io/aureliolo/synthorg-backend@${DIGEST} >/dev/null
             else
-              cat /tmp/cosign-out
+              cat "$COSIGN_OUT"
               exit 1
             fi
           fi

Applies to lines 230–238, 433–441, 636–644.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
set -o pipefail
if ! cosign sign --yes ghcr.io/aureliolo/synthorg-backend@${DIGEST} 2>&1 | tee /tmp/cosign-out; then
if grep -q 'createLogEntryConflict' /tmp/cosign-out; then
echo "::notice::Image already signed in transparency log -- skipping"
else
cat /tmp/cosign-out
exit 1
fi
fi
set -o pipefail
COSIGN_OUT="$(mktemp)"
trap 'rm -f "$COSIGN_OUT"' EXIT
if ! cosign sign --yes ghcr.io/aureliolo/synthorg-backend@${DIGEST} 2>&1 | tee "$COSIGN_OUT"; then
if grep -q 'createLogEntryConflict' "$COSIGN_OUT"; then
echo "::notice::Rekor conflict detected; verifying existing signature"
cosign verify \
--certificate-identity-regexp='github\.com/Aureliolo/synthorg' \
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
ghcr.io/aureliolo/synthorg-backend@${DIGEST} >/dev/null
else
cat "$COSIGN_OUT"
exit 1
fi
fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/docker.yml around lines 230 - 238, The Rekor conflict
branch currently skips on 'createLogEntryConflict' without confirming the image
is actually signed; update the conflict-handling branch that greps for
'createLogEntryConflict' (the block around the cosign sign invocation and
/tmp/cosign-out) to run a cosign verify for the same image digest (${DIGEST})
before treating it as success: call cosign verify with certificate
identity/issuer constraints (e.g. --certificate-identity "<OIDC_SUBJECT>"
--certificate-issuer "<OIDC_ISSUER>") against
ghcr.io/aureliolo/synthorg-backend@${DIGEST} and fail the step if verify returns
non-zero, otherwise print a notice and continue; apply this same change to the
other two sign steps that use the same pattern (grep 'createLogEntryConflict' on
/tmp/cosign-out).

@Aureliolo
Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants