Skip to content

feat: extend approval, meeting, and budget API responses#834

Merged
Aureliolo merged 10 commits intomainfrom
feat/approval-meeting-api-extensions
Mar 26, 2026
Merged

feat: extend approval, meeting, and budget API responses#834
Aureliolo merged 10 commits intomainfrom
feat/approval-meeting-api-extensions

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Approval responses enriched with seconds_remaining (computed from expires_at - now) and urgency_level enum (critical/high/normal/no_expiry) on all 5 endpoints
  • Meeting responses enriched with token_usage_by_participant, contribution_rank (sorted desc), and meeting_duration_seconds on all 3 endpoints
  • Budget GET /records returns CostRecordListResponse with daily_summary (per-day aggregation) and period_summary (avg cost, total tokens) computed from all matching records
  • Frontend types updated: ApprovalResponse, MeetingResponse, UrgencyLevel, DailySummary, PeriodSummary
  • Design spec updated: approval urgency enrichment, meeting analytics, budget summaries

Test plan

  • 29 new/updated unit tests across all three controller test files
  • Boundary tests for urgency thresholds (exactly 1hr, exactly 4hrs)
  • Edge cases: expired approval (seconds_remaining=0.0), empty contributions, non-completed meetings
  • Summary aggregation: daily grouping, period average, summaries from all records (not just page)
  • Full suite: 10,631 Python tests pass, 223 frontend tests pass
  • mypy strict: clean across 1,202 source files
  • ruff lint + format: clean
  • Frontend lint + type-check: clean
  • OpenAPI schema regenerated with 11 new types

Review coverage

Pre-reviewed by 9 agents (code-reviewer, type-design-analyzer, test-analyzer, comment-analyzer, conventions-enforcer, logging-audit, api-contract-drift, docs-consistency, issue-resolution-verifier). 17 findings addressed:

  • 3 Major: error_detail validator, @computed_field for avg_cost, NotBlankStr for date
  • 14 Medium: ge=0.0 constraints, boundary tests, edge case tests, frontend types, design spec docs, pre-existing log event fix

Closes #774

🤖 Generated with Claude Code

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the API responses for approvals, meetings, and budget records by introducing computed, client-side-ready analytics and urgency indicators. These additions aim to provide richer data directly from the API, reducing the need for frontend computation and enabling more insightful dashboards and user interfaces. The changes span backend controllers, data models, and corresponding frontend type definitions, ensuring a consistent and robust data flow for these extended features.

Highlights

  • Approval API Response Enrichment: Approval API responses now include seconds_remaining (computed from expires_at - now) and an urgency_level enum (critical, high, normal, no_expiry) across all 5 approval endpoints.
  • Meeting API Response Enrichment: Meeting API responses are enriched with token_usage_by_participant, contribution_rank (sorted by token usage descending), and meeting_duration_seconds on all 3 meeting endpoints.
  • Budget Records API Summaries: The Budget GET /records endpoint now returns daily_summary (per-day aggregation) and period_summary (overall average cost, total tokens) computed from all matching records, not just the current page.
  • Frontend Type Updates: Frontend types (ApprovalResponse, MeetingResponse, UrgencyLevel, DailySummary, PeriodSummary) have been updated to align with the new, enriched API response structures.
  • Design Specification Updates: Design specifications for approval urgency enrichment, meeting analytics, and budget summaries have been added to the documentation.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 26, 2026 08:37 — with GitHub Actions Inactive
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 3462f49.
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.

Scanned Files

None

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 API response enrichment across several domains. The Meeting API now includes token_usage_by_participant, contribution_rank, and meeting_duration_seconds in MeetingRecord responses. The Budget API's /budget/records endpoint is enhanced with daily_summary and period_summary aggregations. Additionally, the Approval API now provides seconds_remaining and urgency_level fields in ApprovalItem responses, along with a new APPROVAL_GATE_REVIEW_TRANSITION_FAILED event. Feedback suggests improving the _to_approval_response function for consistency and testability by passing the current time as a parameter, and optimizing date formatting in the budget controller for better performance.

Comment on lines +97 to +122
def _to_approval_response(item: ApprovalItem) -> ApprovalResponse:
"""Convert an ApprovalItem to an ApprovalResponse with urgency fields.

Args:
item: The domain-layer approval item.

Returns:
Response DTO with computed ``seconds_remaining`` and ``urgency_level``.
"""
now = datetime.now(UTC)
if item.expires_at is None:
seconds_remaining = None
urgency = UrgencyLevel.NO_EXPIRY
else:
seconds_remaining = max(0.0, (item.expires_at - now).total_seconds())
if seconds_remaining < _URGENCY_CRITICAL_SECONDS:
urgency = UrgencyLevel.CRITICAL
elif seconds_remaining < _URGENCY_HIGH_SECONDS:
urgency = UrgencyLevel.HIGH
else:
urgency = UrgencyLevel.NORMAL
return ApprovalResponse(
**item.model_dump(),
seconds_remaining=seconds_remaining,
urgency_level=urgency,
)
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

For consistency and testability, it's better to fetch the current time once and pass it into this function, rather than calling datetime.now(UTC) inside. This is especially important when processing a list of items, as it ensures all items are evaluated against the same timestamp.

I suggest modifying the function signature to accept now as a parameter. You will need to update the call sites. For example:

# In list_approvals
now = datetime.now(UTC)
enriched = tuple(_to_approval_response(i, now=now) for i in page)

# In other methods
return ApiResponse(data=_to_approval_response(item, now=datetime.now(UTC)))
def _to_approval_response(item: ApprovalItem, *, now: datetime) -> ApprovalResponse:
    """Convert an ApprovalItem to an ApprovalResponse with urgency fields.

    Args:
        item: The domain-layer approval item.
        now: The current time to compare against.

    Returns:
        Response DTO with computed ``seconds_remaining`` and ``urgency_level``.
    """
    if item.expires_at is None:
        seconds_remaining = None
        urgency = UrgencyLevel.NO_EXPIRY
    else:
        seconds_remaining = max(0.0, (item.expires_at - now).total_seconds())
        if seconds_remaining < _URGENCY_CRITICAL_SECONDS:
            urgency = UrgencyLevel.CRITICAL
        elif seconds_remaining < _URGENCY_HIGH_SECONDS:
            urgency = UrgencyLevel.HIGH
        else:
            urgency = UrgencyLevel.NORMAL
    return ApprovalResponse(
        **item.model_dump(),
        seconds_remaining=seconds_remaining,
        urgency_level=urgency,
    )


by_day: dict[str, list[CostRecord]] = defaultdict(list)
for r in records:
by_day[r.timestamp.strftime("%Y-%m-%d")].append(r)
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

For performance, it's generally better to use .date().isoformat() instead of strftime("%Y-%m-%d") for extracting the date part of a datetime object. isoformat() is typically faster.

Suggested change
by_day[r.timestamp.strftime("%Y-%m-%d")].append(r)
by_day[r.timestamp.date().isoformat()].append(r)

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 26, 2026

Codecov Report

❌ Patch coverage is 93.79845% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.33%. Comparing base (4252fac) to head (3462f49).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/api/controllers/approvals.py 92.59% 3 Missing and 1 partial ⚠️
src/synthorg/api/controllers/budget.py 92.30% 3 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #834      +/-   ##
==========================================
+ Coverage   92.31%   92.33%   +0.02%     
==========================================
  Files         584      584              
  Lines       30491    30576      +85     
  Branches     2934     2941       +7     
==========================================
+ Hits        28148    28233      +85     
  Misses       1854     1854              
  Partials      489      489              

☔ 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.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Walkthrough

Adds computed response enrichments, DTOs, controller helpers, docs, client types, observability events, and tests. Approval endpoints now return ApprovalResponse with seconds_remaining and urgency_level and centralize approval lookup/decision save logic. Meeting endpoints now return MeetingResponse with token_usage_by_participant, contribution_rank, and meeting_duration_seconds. /budget/records now returns CostRecordListResponse including daily_summary, period_summary, success, and server-side aggregation across all matching records. TypeScript types and API endpoint wrappers updated; unit tests and documentation updated to cover new fields and response shapes.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: extending approval, meeting, and budget API responses with new enriched fields.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset, detailing all three API response extensions, test coverage, and design spec updates.
Linked Issues check ✅ Passed All primary objectives from issue #774 are met: approval responses include seconds_remaining and urgency_level with correct thresholds, meeting responses include token_usage_by_participant, contribution_rank, and meeting_duration_seconds, and budget responses include daily_summary and period_summary aggregations.
Out of Scope Changes check ✅ Passed All code changes are directly aligned with issue #774 objectives. Documentation updates, new event constants, and frontend type definitions are all within scope for the API response extension feature.
Docstring Coverage ✅ Passed Docstring coverage is 43.24% 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.

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/communication.md`:
- Around line 420-428: The opening sentence incorrectly states that all
enrichment fields on MeetingRecord are derived from
MeetingMinutes.contributions; update the text to separate contribution-derived
analytics from duration by either narrowing the sentence to mention only the
contribution-based fields (`token_usage_by_participant`, `contribution_rank`)
come from `MeetingMinutes.contributions` and then add a second sentence
clarifying that `meeting_duration_seconds` is computed from `ended_at -
started_at` (or `null` when minutes are missing); ensure the identifiers
`MeetingRecord`, `MeetingMinutes.contributions`, `token_usage_by_participant`,
`contribution_rank`, `meeting_duration_seconds`, `ended_at`, and `started_at`
are used exactly as in the spec so readers can locate the data sources.

In `@src/synthorg/api/controllers/meetings.py`:
- Around line 116-125: The analytics block in _to_meeting_response emits
participant analytics whenever record.minutes exists and keys them by
MeetingContribution.agent_id, which can leak ID-keyed partial analytics for
non-completed meetings; change the logic to only compute and include
participant-name analytics and meeting_duration_seconds when record.status ==
MeetingStatus.COMPLETED, resolving each MeetingContribution.agent_id to the
participant display name (lookup via the meeting/participant records in the same
response path) before adding to usage, and omit or leave empty these analytics
fields for non-COMPLETED records to match the documented response contract.

In `@tests/unit/api/controllers/test_approvals.py`:
- Around line 571-621: Patch the clock used by the urgency calculation at the
start of the timing-sensitive tests (test_urgency_boundary_at_1hr and
test_urgency_boundary_at_4hrs) so the assertions are deterministic: mock
time.monotonic() to return a fixed value and mock asyncio.sleep() to a no-op
(using monkeypatch or pytest fixtures), then compute the
ApprovalItem.created_at/expires_at relative to that frozen clock before calling
approval_store.add and the GET; this ensures the urgency calculation (which
reads time.monotonic()) yields stable "high" and "normal" results for
ApprovalItem IDs "boundary-1h" and "boundary-4h".

In `@tests/unit/api/controllers/test_budget.py`:
- Around line 174-201: Update the test_summaries_from_all_records_not_page to
include a filtered-summary case: create multiple CostRecord entries including at
least one non-matching record (e.g., agent_id="bob") alongside matching ones
(agent_id="alice"), then call GET "/api/v1/budget/records" with a filter param
(agent_id="alice") and a pagination param (limit=1) and assert that body["data"]
length == 1 (page) but body["period_summary"]["record_count"] and
["total_cost_usd"] reflect only the matching records (sum/count for alice),
ensuring aggregation is applied after filtering by agent_id/task_id.

In `@web/src/api/endpoints/budget.ts`:
- Around line 22-45: The manual unwrap in listCostRecords bypasses response
validation and drops server error_detail; replace the custom body handling with
the shared unwrapping helper (use unwrapPaginated or unwrap that matches
paginated shape) to validate structure and throw errors that include
body.error_detail, or if keeping ApiRequestError, construct it using body.error
and body.error_detail; update listCostRecords to call
apiClient.get<CostRecordListResponseBody> and pass the response into
unwrapPaginated (or call unwrap and map to the paginated result) so
daily_summary and period_summary are validated and ApiRequestError includes
error_detail from the server.
🪄 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: 82e53224-01e2-46a1-9ab0-5ae61a6994c4

📥 Commits

Reviewing files that changed from the base of the PR and between d4dde90 and bdeeb37.

📒 Files selected for processing (13)
  • docs/design/communication.md
  • docs/design/operations.md
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/observability/events/approval_gate.py
  • tests/unit/api/controllers/test_approvals.py
  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_meetings.py
  • web/src/api/endpoints/approvals.ts
  • web/src/api/endpoints/budget.ts
  • web/src/api/endpoints/meetings.ts
  • web/src/api/types.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.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: syntax without parentheses for exception handling (PEP 758) -- ruff enforces this on Python 3.14
Add type hints to all public functions and classes; mypy strict mode required
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules)
Immutability enforcement: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping. For dict/list fields in frozen Pydantic models, rely on frozen=True and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization)
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use @computed_field for derived values instead of storing redundant fields; use NotBlankStr 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 (multiple tool invocations, parallel agent calls). Use structured concurrency over bare create_task
Keep functions under 50 lines, files under 800 lines
Line length: 88 characters (ruff enforced)
Explicitly handle all errors; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, config files

Files:

  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_meetings.py
  • tests/unit/api/controllers/test_approvals.py
  • src/synthorg/observability/events/approval_gate.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Mark all tests with @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, or @pytest.mark.slow
Always run pytest with -n auto for parallelism via pytest-xdist; never run tests sequentially. Minimum coverage: 80% (enforced in CI). Timeout: 30 seconds per test globally (do not add per-file markers; non-default overrides like timeout(60) are allowed)
Vendor-agnostic naming 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. Vendor names allowed only in: (1) docs/design/operations.md provider list, (2) .claude/ skill/agent files, (3) third-party import paths. Tests must use test-provider, test-small-001, etc.
Use Hypothesis for property-based testing (@given + @settings with HYPOTHESIS_PROFILE env var: 'ci' for 50 examples (default), 'dev' for 1000 examples). Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties. .hypothesis/ is gitignored
Never skip, dismiss, or ignore flaky tests -- always fix them fully. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() for determinism. For tasks blocking indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number) -- it is cancellation-safe

Files:

  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_meetings.py
  • tests/unit/api/controllers/test_approvals.py
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{ts,tsx}: Use vendor-agnostic naming: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, or tests. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001
React 19 with shadcn/ui + Tailwind CSS 4. Use React hooks (auth, login lockout, WebSocket, polling, optimistic updates), Zustand stores, and lazy-loaded page components. Maintain component organization: ui/ (shadcn primitives), layout/ (app shell, sidebar, status bar), feature dirs for pages
Never skip, dismiss, or ignore flaky tests -- always fix them fully. Mock timing functions for determinism

Files:

  • web/src/api/endpoints/budget.ts
  • web/src/api/endpoints/approvals.ts
  • web/src/api/types.ts
  • web/src/api/endpoints/meetings.ts
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Use logger.info(EVENT, key=value) with structured kwargs for logging; never use logger.info('msg %s', val) formatting
Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py and observability/sinks.py may use stdlib logging and print() for bootstrap code only
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). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
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. RetryExhaustedError signals all retries failed -- engine layer catches this to trigger fallback chains. Rate limiter respects RateLimitError.retry_after
Always read the relevant docs/design/ page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file. When a spec topic is referenced (e.g., 'the Agents page'), read the relevant docs/design/ page before coding
If implementation deviates from the design spec (better approach, scope evolved), alert the user and explain why. User decides whether to proceed or update spec. Do NOT silently diverge. When approved deviations occur, update relevant docs/design/ page to reflect new reality

Files:

  • src/synthorg/observability/events/approval_gate.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Documentation is built with Zensical (config: mkdocs.yml). Docs structure: docs/design/ (10 pages), docs/architecture/, docs/roadmap/, docs/security.md, docs/licensing.md, docs/reference/, docs/rest-api.md + docs/_generated/api-reference.html (generated by scripts/export_openapi.py), docs/api/ (auto-generated via mkdocstrings + Griffe AST-based). Landing page: site/ (Astro with /get/ CLI install page, contact form, SEO)

Files:

  • docs/design/communication.md
  • docs/design/operations.md
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Design spec pages are the mandatory starting point for architecture, data models, and behavior. Always read the relevant docs/design/ page before implementing features. If implementation deviates from spec, alert the user with explanation. Update docs/design/ pages when deviations are approved

Files:

  • docs/design/communication.md
  • docs/design/operations.md
🧠 Learnings (8)
📚 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:

  • tests/unit/api/controllers/test_budget.py
  • web/src/api/endpoints/budget.ts
  • docs/design/operations.md
  • src/synthorg/api/controllers/budget.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/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • tests/unit/api/controllers/test_meetings.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:

  • tests/unit/api/controllers/test_meetings.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:

  • tests/unit/api/controllers/test_meetings.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: 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:

  • web/src/api/endpoints/budget.ts
  • docs/design/operations.md
  • src/synthorg/api/controllers/budget.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/api/**/*.py : Authentication uses JWT + API key. Approval gate integration for high-risk operations.

Applied to files:

  • src/synthorg/observability/events/approval_gate.py
  • docs/design/operations.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/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:

  • docs/design/operations.md
  • src/synthorg/api/controllers/budget.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/approvals.py
🪛 markdownlint-cli2 (0.22.0)
docs/design/operations.md

[warning] 1008-1008: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

🔇 Additional comments (4)
tests/unit/api/controllers/test_meetings.py (1)

33-89: Analytics coverage is nicely targeted.

The new fixtures exercise populated, empty, failed, and trigger paths, and the fixed 120-second delta keeps the duration assertion deterministic.

Also applies to: 123-137, 272-287, 322-439

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

32-32: The shared frontend types match the enriched payloads.

This keeps approval urgency, meeting analytics, and budget summaries typed end-to-end instead of forcing downstream casts.

Also applies to: 279-282, 388-402, 765-769

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

62-123: Urgency enrichment is applied consistently across all approval endpoints.

Using a single converter everywhere keeps the response shape uniform, and clamping seconds_remaining at zero avoids negative values for expired items.

Also applies to: 319-324, 398-424, 431-455, 462-523, 535-686

web/src/api/endpoints/approvals.ts (1)

12-34: The endpoint wrappers now expose the richer approval DTO cleanly.

Callers can consume seconds_remaining and urgency_level without local re-typing.

Comment on lines +116 to +125
if record.minutes is not None:
for c in record.minutes.contributions:
usage[c.agent_id] = (
usage.get(c.agent_id, 0) + c.input_tokens + c.output_tokens
)
rank = tuple(
sorted(usage, key=usage.__getitem__, reverse=True),
)
delta = record.minutes.ended_at - record.minutes.started_at
duration = delta.total_seconds()
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

Return participant names only for completed meetings.

_to_meeting_response() currently emits analytics whenever minutes exists and uses MeetingContribution.agent_id as the outward-facing key. The PR objective says these fields are participant-name analytics and meeting_duration_seconds is completed-only, so a failed/cancelled record with partial minutes would leak ID-keyed analytics that don't match the documented response contract. Gate this block on record.status == MeetingStatus.COMPLETED and resolve IDs to display names here, or update the API/docs/types/tests to explicitly contract on IDs instead.

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

In `@src/synthorg/api/controllers/meetings.py` around lines 116 - 125, The
analytics block in _to_meeting_response emits participant analytics whenever
record.minutes exists and keys them by MeetingContribution.agent_id, which can
leak ID-keyed partial analytics for non-completed meetings; change the logic to
only compute and include participant-name analytics and meeting_duration_seconds
when record.status == MeetingStatus.COMPLETED, resolving each
MeetingContribution.agent_id to the participant display name (lookup via the
meeting/participant records in the same response path) before adding to usage,
and omit or leave empty these analytics fields for non-COMPLETED records to
match the documented response contract.

Comment on lines +571 to +621
async def test_urgency_boundary_at_1hr(
self,
test_client: TestClient[Any],
approval_store: ApprovalStore,
) -> None:
now = datetime.now(UTC)
item = ApprovalItem(
id="boundary-1h",
action_type="code_merge",
title="Boundary test",
description="desc",
requested_by="agent-dev",
risk_level=ApprovalRiskLevel.MEDIUM,
created_at=now,
# 3605s in the future -- after processing the remaining
# time will still be >= 3600s, landing in HIGH (not critical).
expires_at=now + timedelta(seconds=3605),
)
await approval_store.add(item)
resp = test_client.get(
f"{_BASE}/boundary-1h",
headers=_READ_HEADERS,
)
data = resp.json()["data"]
assert data["urgency_level"] == "high"

async def test_urgency_boundary_at_4hrs(
self,
test_client: TestClient[Any],
approval_store: ApprovalStore,
) -> None:
now = datetime.now(UTC)
item = ApprovalItem(
id="boundary-4h",
action_type="code_merge",
title="Boundary test",
description="desc",
requested_by="agent-dev",
risk_level=ApprovalRiskLevel.MEDIUM,
created_at=now,
# 14405s in the future -- after processing the remaining
# time will still be >= 14400s, landing in NORMAL (not high).
expires_at=now + timedelta(seconds=14405),
)
await approval_store.add(item)
resp = test_client.get(
f"{_BASE}/boundary-4h",
headers=_READ_HEADERS,
)
data = resp.json()["data"]
assert data["urgency_level"] == "normal"
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

Freeze time for the threshold boundaries.

The 5-second buffers at Line 587 and Line 613 still make these assertions dependent on runtime speed. Slow CI can flip the 1-hour case into critical or the 4-hour case into high, so please mock the clock the urgency calculation reads instead of relying on wall-clock slack.

As per coding guidelines, "For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() for determinism."

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

In `@tests/unit/api/controllers/test_approvals.py` around lines 571 - 621, Patch
the clock used by the urgency calculation at the start of the timing-sensitive
tests (test_urgency_boundary_at_1hr and test_urgency_boundary_at_4hrs) so the
assertions are deterministic: mock time.monotonic() to return a fixed value and
mock asyncio.sleep() to a no-op (using monkeypatch or pytest fixtures), then
compute the ApprovalItem.created_at/expires_at relative to that frozen clock
before calling approval_store.add and the GET; this ensures the urgency
calculation (which reads time.monotonic()) yields stable "high" and "normal"
results for ApprovalItem IDs "boundary-1h" and "boundary-4h".

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: 3

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/approvals.py (1)

481-487: ⚠️ Potential issue | 🟡 Minor

Remove duplicate validation or derive the max_length annotation from QUERY_MAX_LENGTH.

action_type is declared as Annotated[str, Parameter(max_length=128)], so Litestar will reject overlong query values at the framework level before this handler executes. The manual validation on lines 503–509 and its API_VALIDATION_FAILED log will not fire for HTTP request validation failures, making that instrumentation ineffective for the path you're trying to monitor. Additionally, the hardcoded 128 can drift from QUERY_MAX_LENGTH.

Either derive the annotation limit from the QUERY_MAX_LENGTH constant or remove the duplicate controller-level check if it only exists to catch HTTP path violations.

Also applies to: 503-509

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

In `@src/synthorg/api/controllers/approvals.py` around lines 481 - 487, The
controller duplicates validation for action_type in list_approvals: the
Parameter(max_length=128) annotation already enforces length via Litestar so the
manual check and API_VALIDATION_FAILED logging (the block around the manual
validation in list_approvals) is redundant and uses a hardcoded 128. Fix by
either (A) deriving the annotated max_length from QUERY_MAX_LENGTH (use
Parameter(max_length=QUERY_MAX_LENGTH) for action_type) so framework and
controller use the same limit, or (B) remove the manual length-check block and
its API_VALIDATION_FAILED log inside list_approvals entirely if you only need
framework-level validation; ensure any remaining instrumentation references
QUERY_MAX_LENGTH rather than the hardcoded 128.
♻️ Duplicate comments (2)
web/src/api/endpoints/budget.ts (1)

36-47: ⚠️ Potential issue | 🟠 Major

Validate the success payload before returning it.

This path only checks success. If pagination, daily_summary, or period_summary are missing or malformed, the function returns undefined fields and the UI fails later instead of raising here.

🛠️ Minimal hardening
   const response = await apiClient.get<CostRecordListResponseBody>('/budget/records', { params })
   const body = response.data
-  if (!body?.success) {
-    throw new ApiRequestError(body?.error ?? 'Unknown API error', body?.error_detail ?? null)
+  if (!body || typeof body !== 'object') {
+    throw new ApiRequestError('Unexpected API response format')
+  }
+  if (!body.success) {
+    throw new ApiRequestError(body.error ?? 'Unknown API error', body.error_detail ?? null)
+  }
+  if (
+    !Array.isArray(body.data) ||
+    !body.pagination ||
+    !Array.isArray(body.daily_summary) ||
+    !body.period_summary
+  ) {
+    throw new ApiRequestError('Unexpected API response format')
   }
   return {
     data: body.data,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/api/endpoints/budget.ts` around lines 36 - 47, The function that
calls apiClient.get<CostRecordListResponseBody>('/budget/records', { params })
currently only checks body?.success; extend validation to ensure required
payload fields (body.data, body.pagination and its keys total/offset/limit, and
body.daily_summary and body.period_summary) are present and of expected types
before returning; if any are missing or malformed, throw an ApiRequestError with
a clear message (include body?.error_detail if available) so callers don't
receive undefined fields—update the handling around the
CostRecordListResponseBody response and the return block to perform these
checks.
src/synthorg/api/controllers/meetings.py (1)

81-105: ⚠️ Potential issue | 🟠 Major

Align meeting analytics with the published contract.

The linked objective says these fields are participant-name analytics for completed meetings. This code still emits analytics for any record that has minutes and uses MeetingContribution.agent_id as the public key, so failed/cancelled records with partial minutes would leak ID-keyed analytics that do not match the contract. Gate the computation on record.status == MeetingStatus.COMPLETED, and resolve IDs to the outward-facing participant names before serializing them. If IDs are intentional, please make that an explicit approved contract change across the spec/types/tests instead of silently diverging.

As per coding guidelines, "If implementation deviates from the design spec (better approach, scope evolved), alert the user and explain why. User decides whether to proceed or update spec."

Also applies to: 124-133

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

In `@src/synthorg/api/controllers/meetings.py` around lines 81 - 105, The
analytics currently emitted from MeetingResponse/meeting serialization are being
produced for any record with minutes and keyed by MeetingContribution.agent_id;
change the logic that computes token_usage_by_participant, contribution_rank,
and meeting_duration_seconds to only run when the MeetingRecord.status equals
MeetingStatus.COMPLETED, and map participant identifiers to their outward-facing
participant names (resolve agent_id -> participant_name) before building
token_usage_by_participant and contribution_rank; if using raw agent IDs is
intentional, surface that as an explicit contract change (update
spec/types/tests) rather than leaving the current 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/approvals.py`:
- Around line 80-97: Convert the derived fields in ApprovalResponse to computed
properties: remove seconds_remaining and urgency_level as regular Fields and
instead add an internal reference_time Field(exclude=True) to hold the passed
"now" value, then implement two `@computed_field` methods on ApprovalResponse that
compute seconds_remaining from expires_at and reference_time and compute
urgency_level from that seconds_remaining (reuse the existing UrgencyLevel
logic). Update the factory _to_approval_response() to set the internal
reference_time (now) on the ApprovalResponse instance rather than injecting
seconds_remaining/urgency_level so instantiation and serialization follow
pydantic computed_field semantics.

In `@src/synthorg/api/controllers/budget.py`:
- Around line 243-248: Change the domain-event log call from debug to info:
replace the logger.debug(...) invocation that emits API_BUDGET_RECORDS_LISTED
with logger.info(...) and keep the structured keyword args (agent_id, task_id,
record_count=len(records)) rather than printf-style formatting so the event is
recorded at info level consistent with the repo’s event-logging contract; update
the call where API_BUDGET_RECORDS_LISTED, logger, agent_id, task_id, and records
are referenced (in the budget listing code path).

In `@tests/unit/api/controllers/test_budget.py`:
- Around line 140-163: Add assertions to check token totals in the
period_summary for the populated and filtered summary tests: after retrieving
period = body["period_summary"] in test_period_summary_avg_cost (and the other
summary tests around lines 205-233), assert period["total_input_tokens"] ==
<expected_total_input> and period["total_output_tokens"] ==
<expected_total_output> (use the computed expected values from the recorded
CostRecord entries, e.g., input_tokens sum 100*3 and output_tokens sum 50*3 for
the three records in test_period_summary_avg_cost). Ensure you update the two
affected tests (the populated summary and the filtered-summary test) to include
these token-total assertions using the same period variable.

---

Outside diff comments:
In `@src/synthorg/api/controllers/approvals.py`:
- Around line 481-487: The controller duplicates validation for action_type in
list_approvals: the Parameter(max_length=128) annotation already enforces length
via Litestar so the manual check and API_VALIDATION_FAILED logging (the block
around the manual validation in list_approvals) is redundant and uses a
hardcoded 128. Fix by either (A) deriving the annotated max_length from
QUERY_MAX_LENGTH (use Parameter(max_length=QUERY_MAX_LENGTH) for action_type) so
framework and controller use the same limit, or (B) remove the manual
length-check block and its API_VALIDATION_FAILED log inside list_approvals
entirely if you only need framework-level validation; ensure any remaining
instrumentation references QUERY_MAX_LENGTH rather than the hardcoded 128.

---

Duplicate comments:
In `@src/synthorg/api/controllers/meetings.py`:
- Around line 81-105: The analytics currently emitted from
MeetingResponse/meeting serialization are being produced for any record with
minutes and keyed by MeetingContribution.agent_id; change the logic that
computes token_usage_by_participant, contribution_rank, and
meeting_duration_seconds to only run when the MeetingRecord.status equals
MeetingStatus.COMPLETED, and map participant identifiers to their outward-facing
participant names (resolve agent_id -> participant_name) before building
token_usage_by_participant and contribution_rank; if using raw agent IDs is
intentional, surface that as an explicit contract change (update
spec/types/tests) rather than leaving the current behavior.

In `@web/src/api/endpoints/budget.ts`:
- Around line 36-47: The function that calls
apiClient.get<CostRecordListResponseBody>('/budget/records', { params })
currently only checks body?.success; extend validation to ensure required
payload fields (body.data, body.pagination and its keys total/offset/limit, and
body.daily_summary and body.period_summary) are present and of expected types
before returning; if any are missing or malformed, throw an ApiRequestError with
a clear message (include body?.error_detail if available) so callers don't
receive undefined fields—update the handling around the
CostRecordListResponseBody response and the return block to perform these
checks.
🪄 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: 83556eaa-c7a4-4ce0-be68-0fd7f7009e41

📥 Commits

Reviewing files that changed from the base of the PR and between bdeeb37 and 48bb1a3.

📒 Files selected for processing (9)
  • docs/design/communication.md
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/observability/events/api.py
  • tests/unit/api/controllers/test_approvals.py
  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_meetings.py
  • web/src/api/endpoints/budget.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). (4)
  • GitHub Check: Build Backend
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (6)
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Documentation is built with Zensical (config: mkdocs.yml). Docs structure: docs/design/ (10 pages), docs/architecture/, docs/roadmap/, docs/security.md, docs/licensing.md, docs/reference/, docs/rest-api.md + docs/_generated/api-reference.html (generated by scripts/export_openapi.py), docs/api/ (auto-generated via mkdocstrings + Griffe AST-based). Landing page: site/ (Astro with /get/ CLI install page, contact form, SEO)

Files:

  • docs/design/communication.md
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Design spec pages are the mandatory starting point for architecture, data models, and behavior. Always read the relevant docs/design/ page before implementing features. If implementation deviates from spec, alert the user with explanation. Update docs/design/ pages when deviations are approved

Files:

  • docs/design/communication.md
**/*.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: syntax without parentheses for exception handling (PEP 758) -- ruff enforces this on Python 3.14
Add type hints to all public functions and classes; mypy strict mode required
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules)
Immutability enforcement: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping. For dict/list fields in frozen Pydantic models, rely on frozen=True and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization)
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use @computed_field for derived values instead of storing redundant fields; use NotBlankStr 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 (multiple tool invocations, parallel agent calls). Use structured concurrency over bare create_task
Keep functions under 50 lines, files under 800 lines
Line length: 88 characters (ruff enforced)
Explicitly handle all errors; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, config files

Files:

  • src/synthorg/observability/events/api.py
  • src/synthorg/api/controllers/budget.py
  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_approvals.py
  • src/synthorg/api/controllers/approvals.py
  • tests/unit/api/controllers/test_meetings.py
  • src/synthorg/api/controllers/meetings.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Use logger.info(EVENT, key=value) with structured kwargs for logging; never use logger.info('msg %s', val) formatting
Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py and observability/sinks.py may use stdlib logging and print() for bootstrap code only
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). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
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. RetryExhaustedError signals all retries failed -- engine layer catches this to trigger fallback chains. Rate limiter respects RateLimitError.retry_after
Always read the relevant docs/design/ page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file. When a spec topic is referenced (e.g., 'the Agents page'), read the relevant docs/design/ page before coding
If implementation deviates from the design spec (better approach, scope evolved), alert the user and explain why. User decides whether to proceed or update spec. Do NOT silently diverge. When approved deviations occur, update relevant docs/design/ page to reflect new reality

Files:

  • src/synthorg/observability/events/api.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/meetings.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Mark all tests with @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, or @pytest.mark.slow
Always run pytest with -n auto for parallelism via pytest-xdist; never run tests sequentially. Minimum coverage: 80% (enforced in CI). Timeout: 30 seconds per test globally (do not add per-file markers; non-default overrides like timeout(60) are allowed)
Vendor-agnostic naming 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. Vendor names allowed only in: (1) docs/design/operations.md provider list, (2) .claude/ skill/agent files, (3) third-party import paths. Tests must use test-provider, test-small-001, etc.
Use Hypothesis for property-based testing (@given + @settings with HYPOTHESIS_PROFILE env var: 'ci' for 50 examples (default), 'dev' for 1000 examples). Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties. .hypothesis/ is gitignored
Never skip, dismiss, or ignore flaky tests -- always fix them fully. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() for determinism. For tasks blocking indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number) -- it is cancellation-safe

Files:

  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_approvals.py
  • tests/unit/api/controllers/test_meetings.py
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{ts,tsx}: Use vendor-agnostic naming: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, or tests. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001
React 19 with shadcn/ui + Tailwind CSS 4. Use React hooks (auth, login lockout, WebSocket, polling, optimistic updates), Zustand stores, and lazy-loaded page components. Maintain component organization: ui/ (shadcn primitives), layout/ (app shell, sidebar, status bar), feature dirs for pages
Never skip, dismiss, or ignore flaky tests -- always fix them fully. Mock timing functions for determinism

Files:

  • web/src/api/endpoints/budget.ts
🧠 Learnings (18)
📚 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 : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/api.py
  • src/synthorg/api/controllers/budget.py
📚 Learning: 2026-03-24T22:47:39.223Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T22:47:39.223Z
Learning: Applies to src/synthorg/**/*.py : 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). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/api.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:

  • src/synthorg/observability/events/api.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/observability/events/api.py
  • src/synthorg/api/controllers/budget.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/observability/events/api.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 event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/api.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/observability/events/api.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 : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/api.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 event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/api.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/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/observability/events/api.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.py
  • tests/unit/api/controllers/test_budget.py
  • web/src/api/endpoints/budget.ts
📚 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/controllers/budget.py
  • tests/unit/api/controllers/test_budget.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: 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.py
  • web/src/api/endpoints/budget.ts
📚 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/api/**/*.py : Authentication uses JWT + API key. Approval gate integration for high-risk operations.

Applied to files:

  • src/synthorg/api/controllers/approvals.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/approvals.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/approvals.py
  • tests/unit/api/controllers/test_meetings.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/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • tests/unit/api/controllers/test_meetings.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:

  • tests/unit/api/controllers/test_meetings.py
🔇 Additional comments (1)
src/synthorg/api/controllers/approvals.py (1)

390-471: Nice consolidation of the write-side approval flow.

_get_approval_or_404() and _save_decision_and_notify() remove duplicate not-found/conflict handling from both mutation endpoints while keeping the save_if_pending guard centralized.

Comment on lines +80 to +97
class ApprovalResponse(ApprovalItem):
"""Approval item enriched with computed urgency fields.

Attributes:
seconds_remaining: Seconds until expiry, clamped to 0.0 for
expired items (``None`` if no TTL).
urgency_level: Urgency classification based on time remaining.
"""

model_config = ConfigDict(frozen=True)

seconds_remaining: float | None = Field(
ge=0.0,
description="Seconds until expiry (null if no TTL set)",
)
urgency_level: UrgencyLevel = Field(
description="Urgency classification based on remaining time",
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Pydantic 2.12.5, what is the recommended pattern for exposing derived response fields via @computed_field while keeping any reference-time input excluded from the serialized schema?

💡 Result:

In Pydantic 2.12.5, the recommended pattern for exposing derived response fields via @computed_field while excluding reference-time input fields (e.g., temporary or sensitive inputs used only for computation) from the serialized schema is to use a dedicated response model. Define input fields normally or with Field(exclude=True) if they should never appear in output. Add @computed_field properties for derived fields, which appear in serialization schema (model_json_schema(mode='serialization')) and model_dump output by default. To exclude specific inputs from serialization at runtime (e.g., for API responses), use model_dump(round_trip=True), which excludes all computed fields' dependencies and computed fields themselves, simulating input-only output. For exposing only derived fields alongside select inputs, use model_dump(exclude={'input_field': True}). In 2.12+, exclude_computed_fields=True in model_dump excludes all computed fields while keeping inputs. Example: from pydantic import BaseModel, computed_field, Field class MyModel(BaseModel): input_a: str # reference-time input input_b: int = Field(exclude=True) # always excluded @computed_field @property def derived(self) -> str: return f"Derived from {self.input_a} and {self.input_b}" # For response: exposes derived, excludes inputs if using round_trip or exclude response = MyModel(input_a='foo', input_b=42) print(response.model_dump(round_trip=True)) # {'input_a': 'foo'} excludes derived print(response.model_dump) # {'input_a': 'foo', 'input_b': 42, 'derived': 'Derived from foo and 42'} print(response.model_dump(exclude_computed_fields=True)) # {'input_a': 'foo', 'input_b': 42} excludes derived (2.12+) For API/FastAPI responses, use the model with mode='serialization' implicitly via response_model, where computed fields appear as readOnly in OpenAPI schema, and inputs can be excluded via Field(exclude=True) or dump options. Use separate input/response models for clearest separation: input model without computed fields, response with computed fields referencing inputs post-validation.

Citations:


🏁 Script executed:

cat -n src/synthorg/api/controllers/approvals.py | head -150

Repository: Aureliolo/synthorg

Length of output: 5640


🏁 Script executed:

rg "class ApprovalItem" --type py -A 20

Repository: Aureliolo/synthorg

Length of output: 1605


🏁 Script executed:

rg "ApprovalResponse" --type py -B 2 -A 2

Repository: Aureliolo/synthorg

Length of output: 2980


🏁 Script executed:

rg "_to_approval_response|ApprovalResponse" --type py -B 3 -A 8 | head -100

Repository: Aureliolo/synthorg

Length of output: 6992


Refactor urgency fields to use @computed_field.

seconds_remaining and urgency_level are derived from expires_at and the reference time. Currently stored as regular fields and injected via _to_approval_response(), they should instead be modeled as computed properties. Store the reference time (the now parameter) as an internal excluded field (Field(exclude=True)) and expose the derived values via @computed_field decorators. This prevents inconsistent instantiation and aligns with the guideline: "use @computed_field for derived values instead of storing redundant fields."

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

In `@src/synthorg/api/controllers/approvals.py` around lines 80 - 97, Convert the
derived fields in ApprovalResponse to computed properties: remove
seconds_remaining and urgency_level as regular Fields and instead add an
internal reference_time Field(exclude=True) to hold the passed "now" value, then
implement two `@computed_field` methods on ApprovalResponse that compute
seconds_remaining from expires_at and reference_time and compute urgency_level
from that seconds_remaining (reuse the existing UrgencyLevel logic). Update the
factory _to_approval_response() to set the internal reference_time (now) on the
ApprovalResponse instance rather than injecting seconds_remaining/urgency_level
so instantiation and serialization follow pydantic computed_field semantics.

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/api/controllers/approvals.py`:
- Line 457: The call to _publish_approval_event currently passes the input
variable updated; change it to pass the persisted object saved so the published
event reflects the committed state. Locate the call to
_publish_approval_event(request, ws_event, updated) in approvals.py and replace
the third argument with saved (i.e., _publish_approval_event(request, ws_event,
saved)); ensure saved is the variable returned from the persistence step and is
in scope where the call occurs.
- Around line 504-511: Remove the redundant manual length check that inspects
action_type against QUERY_MAX_LENGTH (the if block that logs
API_VALIDATION_FAILED and raises ApiValidationError) because Litestar's
Parameter(max_length=...) rejects oversized values before the handler runs;
eliminate the equivalent duplicated check in the meetings handler as well.
Instead, rely on Litestar validation and, if you need observability, move
logging into a Litestar exception handler for validation errors rather than
keeping these unreachable manual checks.

In `@src/synthorg/api/controllers/budget.py`:
- Around line 145-187: The _build_summaries function currently iterates records
multiple times; change it to a single-pass aggregation by iterating records once
and updating per-day accumulators in by_day (store running totals:
total_cost_usd using math.fsum accumulator or add to a float,
total_input_tokens, total_output_tokens, record_count) keyed by
r.timestamp.date().isoformat(), while also updating period accumulators
(period_total_cost_usd, period_input_tokens, period_output_tokens,
period_record_count); after the loop, construct DailySummary instances from the
sorted by_day accumulators and build PeriodSummary from the period accumulators,
preserving types and using math.fsum for float accuracy where needed to replace
the separate sum() and math.fsum() passes.

In `@src/synthorg/api/controllers/meetings.py`:
- Around line 175-182: The manual length check for meeting_type is redundant
because Parameter(max_length=QUERY_MAX_LENGTH) already enforces this; remove the
if block that logs API_VALIDATION_FAILED and raises ApiValidationError (the code
referencing meeting_type, QUERY_MAX_LENGTH, API_VALIDATION_FAILED, and
ApiValidationError) so the handler relies on Litestar's validation. If you want
defense-in-depth instead, remove the in-handler check and add a Litestar
exception handler for request validation errors that logs API_VALIDATION_FAILED
with field="meeting_type" and the max length, rather than keeping the dead-code
check inside the endpoint.
🪄 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: 0c65633e-cda7-4118-8d32-24e421300b54

📥 Commits

Reviewing files that changed from the base of the PR and between 48bb1a3 and c13bff5.

📒 Files selected for processing (3)
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/meetings.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: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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: syntax without parentheses for exception handling (PEP 758) -- ruff enforces this on Python 3.14
Add type hints to all public functions and classes; mypy strict mode required
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules)
Immutability enforcement: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping. For dict/list fields in frozen Pydantic models, rely on frozen=True and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization)
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use @computed_field for derived values instead of storing redundant fields; use NotBlankStr 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 (multiple tool invocations, parallel agent calls). Use structured concurrency over bare create_task
Keep functions under 50 lines, files under 800 lines
Line length: 88 characters (ruff enforced)
Explicitly handle all errors; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, config files

Files:

  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/approvals.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Use logger.info(EVENT, key=value) with structured kwargs for logging; never use logger.info('msg %s', val) formatting
Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py and observability/sinks.py may use stdlib logging and print() for bootstrap code only
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). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
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. RetryExhaustedError signals all retries failed -- engine layer catches this to trigger fallback chains. Rate limiter respects RateLimitError.retry_after
Always read the relevant docs/design/ page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file. When a spec topic is referenced (e.g., 'the Agents page'), read the relevant docs/design/ page before coding
If implementation deviates from the design spec (better approach, scope evolved), alert the user and explain why. User decides whether to proceed or update spec. Do NOT silently diverge. When approved deviations occur, update relevant docs/design/ page to reflect new reality

Files:

  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/approvals.py
🧠 Learnings (16)
📚 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.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/controllers/budget.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: 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.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:

  • src/synthorg/api/controllers/budget.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:

  • src/synthorg/api/controllers/budget.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 : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • src/synthorg/api/controllers/budget.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 : Always log with structured kwargs: `logger.info(EVENT, key=value)` — never use old-style formatting `logger.info("msg %s", val)`

Applied to files:

  • src/synthorg/api/controllers/budget.py
📚 Learning: 2026-03-24T22:47:39.223Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T22:47:39.223Z
Learning: Applies to src/synthorg/**/*.py : Use logger.info(EVENT, key=value) with structured kwargs for logging; never use logger.info('msg %s', val) formatting

Applied to files:

  • src/synthorg/api/controllers/budget.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/approvals.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/api/**/*.py : Authentication uses JWT + API key. Approval gate integration for high-risk operations.

Applied to files:

  • src/synthorg/api/controllers/approvals.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/approvals.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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/api/controllers/approvals.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 **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values 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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-24T22:47:39.223Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T22:47:39.223Z
Learning: Applies to **/*.py : Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use computed_field for derived values instead of storing redundant fields; use NotBlankStr for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators

Applied to files:

  • src/synthorg/api/controllers/approvals.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:

  • src/synthorg/api/controllers/approvals.py
🔇 Additional comments (17)
src/synthorg/api/controllers/budget.py (6)

243-248: Log API_BUDGET_RECORDS_LISTED at info level.

Domain events should be logged at info level to appear in the normal observability stream. Using debug will drop this event in production.

📋 Minimal fix
-        logger.debug(
+        logger.info(
             API_BUDGET_RECORDS_LISTED,
             agent_id=agent_id,
             task_id=task_id,
             record_count=len(records),
         )

As per coding guidelines, "Use logger.info(EVENT, key=value) with structured kwargs for logging".


3-25: LGTM — imports are well-organized.

The new imports align with the added functionality: math.fsum for precise floating-point aggregation, defaultdict for grouping, and the appropriate Pydantic/DTO components. Using QUERY_MAX_LENGTH from the shared path_params module and the domain event constant API_BUDGET_RECORDS_LISTED follows project conventions.


44-68: LGTM — DailySummary model is well-structured.

Frozen Pydantic model with proper field constraints (ge=0, ge=0.0), NotBlankStr for the date identifier, and a complete Google-style docstring.


70-101: LGTM — PeriodSummary correctly uses @computed_field for derived value.

The avg_cost_usd computed field properly handles the division-by-zero case (returning 0.0 when record_count == 0). This follows the coding guideline to use @computed_field for derived values instead of storing redundant fields.


103-143: LGTM — CostRecordListResponse enforces error/error_detail consistency.

The model_validator ensures both fields are set together or both are None, and success is correctly derived via @computed_field. The validation logic is bidirectional (lines 130-135), catching both mismatched states.


213-255: LGTM — endpoint correctly computes summaries across all matching records.

The implementation properly:

  1. Fetches all filtered records via get_records (no pagination at service layer)
  2. Computes summaries from the full result set before pagination
  3. Returns paginated data alongside complete daily_summary and period_summary

This matches the PR objective that summaries reflect all matching records, not just the current page.

src/synthorg/api/controllers/meetings.py (5)

124-133: Analytics still gated on minutes presence rather than COMPLETED status.

The previous review flagged that analytics should only be emitted for completed meetings. The current code computes token_usage_by_participant, contribution_rank, and meeting_duration_seconds whenever record.minutes is not None, which could expose partial analytics for failed or cancelled meetings that have minutes populated.


19-22: LGTM!

Event constants correctly imported from the domain-specific module synthorg.observability.events.api.


81-106: LGTM!

The MeetingResponse DTO is well-structured with proper type annotations, immutability via frozen=True, and the ge=0.0 constraint correctly ensures non-negative duration values.


196-226: LGTM!

The get_meeting endpoint correctly returns the enriched MeetingResponse with consistent typing and documentation.


252-258: LGTM!

The trigger endpoint correctly enriches results and logs with structured kwargs using the appropriate event constant.

src/synthorg/api/controllers/approvals.py (6)

80-97: Acknowledge past review: refactor to @computed_field still applicable.

The previous review correctly identified that seconds_remaining and urgency_level are derived values that should use @computed_field per the coding guidelines. The current implementation stores them as regular fields and computes them externally in _to_approval_response().


1-65: LGTM!

Imports and module setup follow the coding guidelines: uses get_logger(__name__), imports event constants from domain-specific modules, and correctly avoids from __future__ import annotations per Python 3.14/PEP 649 requirements.


100-129: LGTM!

The urgency computation logic correctly implements the spec: clamps seconds_remaining to >= 0.0, handles None expiry with NO_EXPIRY, and applies the threshold mapping (critical < 1hr, high < 4hr, normal >= 4hr).


390-416: Good DRY improvement.

The _get_approval_or_404 helper consolidates the repeated pattern of fetching an approval and raising NotFoundError with proper logging. Clean implementation with appropriate type hints and docstring.


524-544: LGTM!

Clean refactoring: uses _get_approval_or_404 helper, properly enriches response with urgency fields via _to_approval_response, and has updated docstring reflecting the new return type.


614-724: LGTM!

Both approve and reject endpoints are cleanly refactored to use the new _get_approval_or_404 and _save_decision_and_notify helpers, eliminating code duplication while preserving proper error handling semantics (NotFoundError, ConflictError). The consistent use of a single now timestamp for both the decision and the response ensures temporal consistency.

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.

♻️ Duplicate comments (2)
src/synthorg/api/controllers/approvals.py (2)

486-487: 🧹 Nitpick | 🔵 Trivial

Verify whether the extra action_type length check is dead code.

The handler already declares Parameter(max_length=QUERY_MAX_LENGTH). If Litestar still enforces that during request binding, the len(action_type) branch never runs and this custom logging path cannot fire. In that case, remove the branch and log validation failures from the framework exception handler instead.

In Litestar, are query parameters declared with `Parameter(max_length=...)` validated before the route handler executes?

Also applies to: 504-511

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

In `@src/synthorg/api/controllers/approvals.py` around lines 486 - 487, The
runtime len(action_type) check appears redundant because the parameter is
declared as Annotated[str, Parameter(max_length=QUERY_MAX_LENGTH)], so first
verify Litestar performs request-time validation for Parameter.max_length (query
binding) and if it does, remove the len(action_type) branch and its custom
log/validation path from the handler (the parameter named action_type) and rely
on the framework's exception handler to log validation failures; if you
intentionally need runtime guarding instead, change the signature to allow
unvalidated input (e.g., Optional[str] without Parameter(max_length=...)) so the
manual len(...) branch is meaningful. Apply the same change to the other similar
block referenced (the action_type handling around lines 504-511).

80-129: 🛠️ Refactor suggestion | 🟠 Major

Expose urgency fields via @computed_field.

seconds_remaining and urgency_level are derived from expires_at plus a reference timestamp, but ApprovalResponse models them as ordinary inputs and _to_approval_response() injects them manually. That makes inconsistent DTO instances possible and loses the response-only semantics the repo expects; keep the reference time excluded/private and derive both fields with @computed_field instead.

In Pydantic v2, what is the recommended pattern for exposing derived response fields via `@computed_field` while keeping the reference-time input excluded from the serialized schema?

As per coding guidelines, "Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use @computed_field for derived values instead of storing redundant fields."

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

In `@src/synthorg/api/controllers/approvals.py` around lines 80 - 129, Replace the
explicit seconds_remaining and urgency_level fields on ApprovalResponse with
`@computed_field` properties that derive their values from expires_at and a
private timestamp; add a PrivateAttr named _now: datetime | None =
PrivateAttr(None) to ApprovalResponse, implement computed_field methods
seconds_remaining(self) -> float | None and urgency_level(self) -> UrgencyLevel
that use self.expires_at and self._now, keep model_config frozen=True, and
update _to_approval_response to build the ApprovalResponse from
item.model_dump(), set the private _now on the instance (e.g.
object.__setattr__(resp, "_now", now)), then return resp so the derived fields
are computed and the reference time is excluded from the schema/serialization.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/synthorg/api/controllers/approvals.py`:
- Around line 486-487: The runtime len(action_type) check appears redundant
because the parameter is declared as Annotated[str,
Parameter(max_length=QUERY_MAX_LENGTH)], so first verify Litestar performs
request-time validation for Parameter.max_length (query binding) and if it does,
remove the len(action_type) branch and its custom log/validation path from the
handler (the parameter named action_type) and rely on the framework's exception
handler to log validation failures; if you intentionally need runtime guarding
instead, change the signature to allow unvalidated input (e.g., Optional[str]
without Parameter(max_length=...)) so the manual len(...) branch is meaningful.
Apply the same change to the other similar block referenced (the action_type
handling around lines 504-511).
- Around line 80-129: Replace the explicit seconds_remaining and urgency_level
fields on ApprovalResponse with `@computed_field` properties that derive their
values from expires_at and a private timestamp; add a PrivateAttr named _now:
datetime | None = PrivateAttr(None) to ApprovalResponse, implement
computed_field methods seconds_remaining(self) -> float | None and
urgency_level(self) -> UrgencyLevel that use self.expires_at and self._now, keep
model_config frozen=True, and update _to_approval_response to build the
ApprovalResponse from item.model_dump(), set the private _now on the instance
(e.g. object.__setattr__(resp, "_now", now)), then return resp so the derived
fields are computed and the reference time is excluded from the
schema/serialization.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a8297a44-e2ec-4e20-a73d-7454f9b3fc74

📥 Commits

Reviewing files that changed from the base of the PR and between c13bff5 and 865502d.

📒 Files selected for processing (1)
  • src/synthorg/api/controllers/approvals.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). (4)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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: syntax without parentheses for exception handling (PEP 758) -- ruff enforces this on Python 3.14
Add type hints to all public functions and classes; mypy strict mode required
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules)
Immutability enforcement: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping. For dict/list fields in frozen Pydantic models, rely on frozen=True and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization)
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use @computed_field for derived values instead of storing redundant fields; use NotBlankStr 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 (multiple tool invocations, parallel agent calls). Use structured concurrency over bare create_task
Keep functions under 50 lines, files under 800 lines
Line length: 88 characters (ruff enforced)
Explicitly handle all errors; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, config files

Files:

  • src/synthorg/api/controllers/approvals.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Use logger.info(EVENT, key=value) with structured kwargs for logging; never use logger.info('msg %s', val) formatting
Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py and observability/sinks.py may use stdlib logging and print() for bootstrap code only
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). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
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. RetryExhaustedError signals all retries failed -- engine layer catches this to trigger fallback chains. Rate limiter respects RateLimitError.retry_after
Always read the relevant docs/design/ page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file. When a spec topic is referenced (e.g., 'the Agents page'), read the relevant docs/design/ page before coding
If implementation deviates from the design spec (better approach, scope evolved), alert the user and explain why. User decides whether to proceed or update spec. Do NOT silently diverge. When approved deviations occur, update relevant docs/design/ page to reflect new reality

Files:

  • src/synthorg/api/controllers/approvals.py
🧠 Learnings (10)
📚 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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/api/controllers/approvals.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 **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values 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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-24T22:47:39.223Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T22:47:39.223Z
Learning: Applies to **/*.py : Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use computed_field for derived values instead of storing redundant fields; use NotBlankStr for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators

Applied to files:

  • src/synthorg/api/controllers/approvals.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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/approvals.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 **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-24T22:47:39.223Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T22:47:39.223Z
Learning: Applies to **/*.py : Validate at system boundaries: user input, external APIs, config files

Applied to files:

  • src/synthorg/api/controllers/approvals.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/approvals.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/api/**/*.py : Authentication uses JWT + API key. Approval gate integration for high-risk operations.

Applied to files:

  • src/synthorg/api/controllers/approvals.py
🔇 Additional comments (3)
src/synthorg/api/controllers/approvals.py (3)

324-331: Good split between resume and review failure events.

Switching this warning to APPROVAL_GATE_REVIEW_TRANSITION_FAILED makes the log taxonomy match the actual code path.


390-471: Nice consolidation of the decision-save path.

Pulling lookup/conflict handling, event publication, logging, and resume signaling into shared helpers removes approve/reject drift and keeps the WebSocket event tied to the persisted approval object.


490-490: Good call routing all five endpoints through one mapper.

Switching every approval handler to ApprovalResponse and _to_approval_response() keeps the payload shape and urgency rules consistent across list/detail/create/approve/reject.

Also applies to: 529-529, 552-552, 625-625, 681-681, 520-522, 543-545, 612-613, 668-668, 724-724

@Aureliolo Aureliolo force-pushed the feat/approval-meeting-api-extensions branch from 865502d to 2bb1958 Compare March 26, 2026 13:25
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 26, 2026 13:28 — 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: 2

♻️ Duplicate comments (6)
web/src/api/endpoints/budget.ts (1)

36-47: ⚠️ Potential issue | 🟠 Major

Validate the success payload before building CostRecordListResult.

This branch only checks success. A malformed success: true body can still push undefined pagination, daily_summary, or period_summary into callers, which is exactly what the shared unwrap helpers protect against.

🛠️ Minimal hardening
   const body = response.data
-  if (!body?.success) {
+  if (!body || typeof body !== 'object') {
+    throw new ApiRequestError('Unknown API error')
+  }
+  if (!body.success) {
     throw new ApiRequestError(body.error ?? 'Unknown API error', body.error_detail ?? null)
   }
+  if (
+    !Array.isArray(body.data) ||
+    !body.pagination ||
+    !Array.isArray(body.daily_summary) ||
+    !body.period_summary
+  ) {
+    throw new ApiRequestError('Unexpected API response format')
+  }
   return {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/api/endpoints/budget.ts` around lines 36 - 47, The current
success-path only checks body.success but can still return a malformed
CostRecordListResult with undefined fields; when body?.success is true, validate
that body.data, body.pagination (and its total/offset/limit),
body.daily_summary, and body.period_summary are present and well-formed before
returning, and if any are missing throw an ApiRequestError (reuse the existing
ApiRequestError thrown above). Prefer using the shared unwrap helpers used
elsewhere (instead of ad-hoc checks) to validate the response shape, referencing
the response variable and body object in budget.ts and ensure the returned
object builds from the validated fields only.
src/synthorg/api/controllers/approvals.py (1)

504-511: 🧹 Nitpick | 🔵 Trivial

Redundant validation: Litestar Parameter(max_length=...) already enforces this.

The Parameter(max_length=QUERY_MAX_LENGTH) at lines 486-487 rejects oversized values during request deserialization, before the handler executes. This manual check is unreachable.

If observability for validation failures is needed, use a Litestar exception handler instead.

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

In `@src/synthorg/api/controllers/approvals.py` around lines 504 - 511, The manual
length check in the handler that inspects action_type against QUERY_MAX_LENGTH
(the if-block that logs API_VALIDATION_FAILED and raises ApiValidationError) is
redundant because Parameter(max_length=...) already enforces this during
deserialization; remove that unreachable if-statement (the logger.warning and
raise ApiValidationError lines) referencing
action_type/QUERY_MAX_LENGTH/API_VALIDATION_FAILED so the handler no longer
performs duplicate validation; if you need observability for such rejections
instead, implement a Litestar exception handler for the
deserialization/validation exceptions rather than keeping this in the handler.
src/synthorg/api/controllers/meetings.py (1)

175-182: 🧹 Nitpick | 🔵 Trivial

Redundant validation: Litestar Parameter(max_length=...) already enforces this.

The Parameter(max_length=QUERY_MAX_LENGTH) at line 160-161 rejects oversized values during request deserialization, before the handler executes. This manual check is unreachable under normal operation.

If observability for validation failures is desired, consider a Litestar exception handler instead.

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

In `@src/synthorg/api/controllers/meetings.py` around lines 175 - 182, The manual
length check for meeting_type is redundant because Litestar's
Parameter(max_length=QUERY_MAX_LENGTH) already enforces this during
deserialization; remove the conditional block that checks len(meeting_type) >
QUERY_MAX_LENGTH and the associated logger.warning/ApiValidationError raise
(references: meeting_type, QUERY_MAX_LENGTH, API_VALIDATION_FAILED,
ApiValidationError, logger.warning) and, if you need observability for such
validation failures, add or reuse a Litestar exception handler for request
validation errors rather than keeping this unreachable in-handler check.
src/synthorg/api/controllers/budget.py (1)

243-248: ⚠️ Potential issue | 🟡 Minor

Log API_BUDGET_RECORDS_LISTED at info level.

This is a domain event; debug level will drop it from the standard observability stream. Use logger.info(...) to match the repo's event-logging contract.

📋 Proposed fix
-        logger.debug(
+        logger.info(
             API_BUDGET_RECORDS_LISTED,
             agent_id=agent_id,
             task_id=task_id,
             record_count=len(records),
         )

As per coding guidelines, "Use structured logging: always logger.info(EVENT, key=value) — never use logger.info('msg %s', val) formatting".

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

In `@src/synthorg/api/controllers/budget.py` around lines 243 - 248, The domain
event API_BUDGET_RECORDS_LISTED is currently logged at debug level; change the
call in the budget controller to logger.info(...) so the event is emitted on the
standard observability stream, keeping the structured-logging form (pass
API_BUDGET_RECORDS_LISTED, agent_id=agent_id, task_id=task_id,
record_count=len(records)) rather than message-formatting; update the single
call around where API_BUDGET_RECORDS_LISTED is used so the fields remain
identical.
tests/unit/api/controllers/test_budget.py (2)

205-233: ⚠️ Potential issue | 🟡 Minor

Add token total assertions to test_summaries_respect_agent_filter.

For consistency with the PeriodSummary contract, include assertions for total_input_tokens and total_output_tokens. With 2 matching alice records (100 input, 50 output each), expected totals are 200 and 100.

🧪 Proposed fix
         # Page has 1 record, summaries cover 2 (alice only, not bob)
         assert len(body["data"]) == 1
         assert body["period_summary"]["record_count"] == 2
         assert body["period_summary"]["total_cost_usd"] == pytest.approx(0.20)
+        assert body["period_summary"]["total_input_tokens"] == 200
+        assert body["period_summary"]["total_output_tokens"] == 100
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/api/controllers/test_budget.py` around lines 205 - 233, Update the
test_summaries_respect_agent_filter async test to also assert the PeriodSummary
token totals: after existing assertions on record_count and total_cost_usd, add
checks that body["period_summary"]["total_input_tokens"] equals 200 and
body["period_summary"]["total_output_tokens"] equals 100 (two alice records of
100 input and 50 output each) so the PeriodSummary contract is fully validated.

140-163: ⚠️ Potential issue | 🟡 Minor

Add token total assertions to test_period_summary_avg_cost.

The test verifies record_count, total_cost_usd, and avg_cost_usd but does not assert the token totals. Given that each record has input_tokens=100 and output_tokens=50, the expected totals are 300 and 150 respectively.

🧪 Proposed fix to add token assertions
         period = body["period_summary"]
         assert period["record_count"] == 3
         assert period["total_cost_usd"] == pytest.approx(0.60)
+        assert period["total_input_tokens"] == 300
+        assert period["total_output_tokens"] == 150
         assert period["avg_cost_usd"] == pytest.approx(0.20)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/api/controllers/test_budget.py` around lines 140 - 163, Add
assertions for input and output token totals to the test_period_summary_avg_cost
test: after obtaining period = body["period_summary"], assert
period["total_input_tokens"] == 300 and assert period["total_output_tokens"] ==
150 (these totals come from three records with input_tokens=100 and
output_tokens=50). Update the test_period_summary_avg_cost function to include
these two assertions so the period summary token aggregates are validated
alongside record_count, total_cost_usd, and avg_cost_usd.
🤖 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/communication.md`:
- Around line 429-432: The documentation omits the non-negative constraint on
meeting_duration_seconds and the controller returns delta.total_seconds()
without clamping; instead update the docs to state that meeting_duration_seconds
is non-negative (ge=0.0) and that when ended_at < started_at the API will clamp
the duration to 0.0, and also modify the controller logic that currently returns
delta.total_seconds() (the code that computes the duration) to clamp negative
values to 0.0 before returning so the behavior matches the Pydantic field
meeting_duration_seconds.

In `@tests/unit/api/controllers/test_approvals.py`:
- Around line 451-505: The three near-duplicate tests
(test_urgency_critical_under_1hr, test_urgency_high_under_4hrs,
test_urgency_normal_over_4hrs) should be collapsed into a pytest parametrized
test that drives TTL/frozen expiry and expected urgency/seconds_remaining
ranges; update the test to use `@pytest.mark.parametrize` with tuples like
(approval_id, ttl_seconds, expected_urgency, min_seconds, max_seconds) and call
the existing helper _seed_item and the same request pattern
(f"{_BASE}/{approval_id}" with _READ_HEADERS) inside one test function,
asserting data["urgency_level"] and the seconds_remaining range accordingly;
apply the same refactor to the other duplicated block referenced (lines
~572-630) so thresholds live in one table and future changes are centralized.

---

Duplicate comments:
In `@src/synthorg/api/controllers/approvals.py`:
- Around line 504-511: The manual length check in the handler that inspects
action_type against QUERY_MAX_LENGTH (the if-block that logs
API_VALIDATION_FAILED and raises ApiValidationError) is redundant because
Parameter(max_length=...) already enforces this during deserialization; remove
that unreachable if-statement (the logger.warning and raise ApiValidationError
lines) referencing action_type/QUERY_MAX_LENGTH/API_VALIDATION_FAILED so the
handler no longer performs duplicate validation; if you need observability for
such rejections instead, implement a Litestar exception handler for the
deserialization/validation exceptions rather than keeping this in the handler.

In `@src/synthorg/api/controllers/budget.py`:
- Around line 243-248: The domain event API_BUDGET_RECORDS_LISTED is currently
logged at debug level; change the call in the budget controller to
logger.info(...) so the event is emitted on the standard observability stream,
keeping the structured-logging form (pass API_BUDGET_RECORDS_LISTED,
agent_id=agent_id, task_id=task_id, record_count=len(records)) rather than
message-formatting; update the single call around where
API_BUDGET_RECORDS_LISTED is used so the fields remain identical.

In `@src/synthorg/api/controllers/meetings.py`:
- Around line 175-182: The manual length check for meeting_type is redundant
because Litestar's Parameter(max_length=QUERY_MAX_LENGTH) already enforces this
during deserialization; remove the conditional block that checks
len(meeting_type) > QUERY_MAX_LENGTH and the associated
logger.warning/ApiValidationError raise (references: meeting_type,
QUERY_MAX_LENGTH, API_VALIDATION_FAILED, ApiValidationError, logger.warning)
and, if you need observability for such validation failures, add or reuse a
Litestar exception handler for request validation errors rather than keeping
this unreachable in-handler check.

In `@tests/unit/api/controllers/test_budget.py`:
- Around line 205-233: Update the test_summaries_respect_agent_filter async test
to also assert the PeriodSummary token totals: after existing assertions on
record_count and total_cost_usd, add checks that
body["period_summary"]["total_input_tokens"] equals 200 and
body["period_summary"]["total_output_tokens"] equals 100 (two alice records of
100 input and 50 output each) so the PeriodSummary contract is fully validated.
- Around line 140-163: Add assertions for input and output token totals to the
test_period_summary_avg_cost test: after obtaining period =
body["period_summary"], assert period["total_input_tokens"] == 300 and assert
period["total_output_tokens"] == 150 (these totals come from three records with
input_tokens=100 and output_tokens=50). Update the test_period_summary_avg_cost
function to include these two assertions so the period summary token aggregates
are validated alongside record_count, total_cost_usd, and avg_cost_usd.

In `@web/src/api/endpoints/budget.ts`:
- Around line 36-47: The current success-path only checks body.success but can
still return a malformed CostRecordListResult with undefined fields; when
body?.success is true, validate that body.data, body.pagination (and its
total/offset/limit), body.daily_summary, and body.period_summary are present and
well-formed before returning, and if any are missing throw an ApiRequestError
(reuse the existing ApiRequestError thrown above). Prefer using the shared
unwrap helpers used elsewhere (instead of ad-hoc checks) to validate the
response shape, referencing the response variable and body object in budget.ts
and ensure the returned object builds from the validated fields only.
🪄 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: 662bf7fb-3e61-4801-a2fa-f5300a0f37e2

📥 Commits

Reviewing files that changed from the base of the PR and between 865502d and 2bb1958.

📒 Files selected for processing (14)
  • docs/design/communication.md
  • docs/design/operations.md
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/observability/events/approval_gate.py
  • tests/unit/api/controllers/test_approvals.py
  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_meetings.py
  • web/src/api/endpoints/approvals.ts
  • web/src/api/endpoints/budget.ts
  • web/src/api/endpoints/meetings.ts
  • 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). (8)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Detect Changes
  • GitHub Check: Dependency Review
  • GitHub Check: Build Preview
  • GitHub Check: Gitleaks
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do NOT use from __future__ import annotations - Python 3.14 has PEP 649 with native lazy annotations
Use except A, B: syntax (no parentheses) per PEP 758 - ruff enforces this on Python 3.14

Files:

  • src/synthorg/observability/events/approval_gate.py
  • src/synthorg/observability/events/api.py
  • tests/unit/api/controllers/test_approvals.py
  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_meetings.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: All public functions must have type hints. Type checking enforced by mypy in strict mode.
Google-style docstrings required on all public classes and functions, enforced by ruff D rules
Use Pydantic v2 conventions: BaseModel, model_validator, computed_field, ConfigDict. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr for all identifier/name fields.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare create_task
Line length must be 88 characters (enforced by ruff)
Functions must be less than 50 lines, files less than 800 lines
Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.
Never use import logging / logging.getLogger() / print() in application code (except observability/setup.py and observability/sinks.py which may use stdlib logging and stderr print for bootstrap)
Always use logger as the variable name (not _logger, not log)
Always use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
Use structured logging: always logger.info(EVENT, key=value) - never use printf-style formatting like logger.info("msg %s", val)
All state transitions must log at INFO level
DEBUG logging is appropriate for object creation, internal flow, and entry/exit of key functions
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, etc. Vendor names only in: (1) Operations design page, (2) .claude/ skill files, (3) third-party import paths.

Files:

  • src/synthorg/observability/events/approval_gate.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • src/synthorg/observability/events/approval_gate.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Documentation uses Markdown and is built with Zensical (config: mkdocs.yml). API reference auto-generated from OpenAPI schema via scripts/export_openapi.py. Library reference auto-generated via mkdocstrings + Griffe.

Files:

  • docs/design/communication.md
  • docs/design/operations.md
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Tests must use test-provider, test-small-001, etc. instead of real vendor names
Use @pytest.mark.parametrize for testing similar cases
Mark all tests with appropriate pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Use Hypothesis for property-based testing in Python with @given and @settings decorators. Run dev profile with 1000 examples via HYPOTHESIS_PROFILE=dev environment variable.
Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks blocking indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/api/controllers/test_approvals.py
  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_meetings.py
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{ts,tsx}: React 19 + shadcn/ui + Tailwind CSS for web dashboard. TypeScript required in web/src/.
Run ESLint with npm --prefix web run lint for React code style and quality checks
Run TypeScript type checking with npm --prefix web run type-check
Use Axios for HTTP requests in the web dashboard
Use @tanstack/react-query for server state management and caching in the web dashboard

Files:

  • web/src/api/endpoints/approvals.ts
  • web/src/api/endpoints/meetings.ts
  • web/src/api/endpoints/budget.ts
  • web/src/api/types.ts
web/src/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Web dashboard API client uses Axios with endpoint modules (18 domains) in web/src/api/

Files:

  • web/src/api/endpoints/approvals.ts
  • web/src/api/endpoints/meetings.ts
  • web/src/api/endpoints/budget.ts
  • web/src/api/types.ts
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/api/**/*.py: REST API defined with Litestar. Errors must use RFC 9457 format in src/synthorg/api/
Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Files:

  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
🧠 Learnings (31)
📚 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/api/**/*.py : Authentication uses JWT + API key. Approval gate integration for high-risk operations.

Applied to files:

  • src/synthorg/observability/events/approval_gate.py
  • docs/design/operations.md
  • src/synthorg/api/controllers/approvals.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 : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/api.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/observability/events/api.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:

  • src/synthorg/observability/events/api.py
  • src/synthorg/api/controllers/budget.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/observability/events/api.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 event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/api.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/observability/events/api.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/api.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 event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/api.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 : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/api.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/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/observability/events/api.py
📚 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 tests/**/*.py : 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

Applied to files:

  • tests/unit/api/controllers/test_approvals.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic. For tasks blocking indefinitely, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`.

Applied to files:

  • tests/unit/api/controllers/test_approvals.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:

  • tests/unit/api/controllers/test_budget.py
  • web/src/api/endpoints/budget.ts
  • docs/design/operations.md
  • src/synthorg/api/controllers/budget.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:

  • tests/unit/api/controllers/test_budget.py
  • docs/design/operations.md
  • src/synthorg/api/controllers/budget.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: 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:

  • web/src/api/endpoints/budget.ts
  • docs/design/operations.md
  • src/synthorg/api/controllers/budget.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/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • tests/unit/api/controllers/test_meetings.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:

  • tests/unit/api/controllers/test_meetings.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:

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

Applied to files:

  • tests/unit/api/controllers/test_meetings.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:

  • src/synthorg/api/controllers/budget.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 : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • src/synthorg/api/controllers/budget.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 : Always log with structured kwargs: `logger.info(EVENT, key=value)` — never use old-style formatting `logger.info("msg %s", val)`

Applied to files:

  • src/synthorg/api/controllers/budget.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/approvals.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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/api/controllers/approvals.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 **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values 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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. Use `computed_field` for derived values instead of storing redundant fields. Use `NotBlankStr` for all identifier/name fields.

Applied to files:

  • src/synthorg/api/controllers/approvals.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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/approvals.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 **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/api/controllers/approvals.py
🪛 markdownlint-cli2 (0.22.0)
docs/design/operations.md

[warning] 1008-1008: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

🔇 Additional comments (17)
tests/unit/api/controllers/test_budget.py (1)

235-340: LGTM!

The TestCostRecordListResponseValidator class thoroughly validates the CostRecordListResponse model constraints: error/error_detail consistency and the computed success field derivation.

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

32-32: LGTM!

The UrgencyLevel type correctly mirrors the backend UrgencyLevel enum values.


279-282: LGTM!

ApprovalResponse correctly extends ApprovalItem with the computed urgency fields, matching the backend ApprovalResponse model.


388-402: LGTM!

DailySummary and PeriodSummary interfaces correctly mirror the backend Pydantic models with appropriate field types.


765-769: LGTM!

MeetingResponse correctly extends MeetingRecord with analytics fields. The readonly modifier on contribution_rank appropriately matches the backend's immutable tuple type.

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

33-66: LGTM!

The _create_meeting_test_app helper centralizes test app creation with proper dependency injection, improving maintainability across meeting controller tests.


69-125: LGTM!

The _make_minutes helper with with_contributions parameter enables deterministic analytics testing. The fixed 120-second duration (started = now - timedelta(seconds=120)) ensures stable duration assertions.


348-423: LGTM!

TestMeetingAnalytics provides comprehensive coverage for the new analytics fields:

  • Token aggregation by participant (agent-alpha: 450, agent-beta: 400)
  • Contribution ranking sorted descending
  • Duration computation from timestamps
  • Empty analytics for non-completed meetings
  • Duration presence even with empty contributions
src/synthorg/api/controllers/meetings.py (2)

81-106: LGTM!

The MeetingResponse model correctly extends MeetingRecord with analytics fields. The ge=0.0 constraint on meeting_duration_seconds prevents negative durations.


108-140: LGTM!

The _to_meeting_response function correctly:

  • Aggregates tokens by agent_id (input + output)
  • Sorts participants by total tokens descending for contribution_rank
  • Computes duration from ended_at - started_at
  • Returns empty analytics when minutes is None
src/synthorg/api/controllers/budget.py (3)

44-100: LGTM!

DailySummary and PeriodSummary models are well-designed:

  • Appropriate ge=0 constraints on all numeric fields
  • avg_cost_usd as @computed_field follows the guideline for derived values
  • Division-by-zero handled in avg_cost_usd property

103-142: LGTM!

CostRecordListResponse correctly enforces the error/error_detail consistency invariant via @model_validator and derives success from error via @computed_field.


145-186: LGTM!

The _build_summaries function correctly:

  • Handles empty records with zero-valued summary
  • Groups by ISO date string for daily aggregation
  • Uses math.fsum for floating-point accuracy
  • Returns chronologically sorted daily summaries
src/synthorg/api/controllers/approvals.py (4)

63-77: LGTM!

The UrgencyLevel enum and threshold constants are well-defined. Using StrEnum ensures JSON serialization produces lowercase string values matching the documented contract.


80-129: LGTM!

The ApprovalResponse model and _to_approval_response function correctly implement the urgency computation:

  • seconds_remaining clamped to 0.0 for expired items
  • Urgency levels correctly bucketed by thresholds
  • None handling for items without expiry

186-194: LGTM!

The except MemoryError, RecursionError: syntax follows PEP 758 for Python 3.14, as required by coding guidelines.


390-471: LGTM!

The _get_approval_or_404 and _save_decision_and_notify helpers consolidate common logic:

  • Consistent 404 handling with warning logging
  • Conflict detection via save_if_pending return value
  • Proper sequencing: persist → publish → log → resume

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: 3

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/approvals.py (1)

503-505: 🧹 Nitpick | 🔵 Trivial

Manual validation is redundant but intentionally retained.

The Parameter(max_length=QUERY_MAX_LENGTH) constraint on line 485-486 validates before the handler executes. However, per the commit message, this manual check was intentionally restored "to avoid framework error behavior" — likely to provide consistent RFC 9457 error responses or observability logging.

If the intent is observability, consider moving this logic to a Litestar exception handler for cleaner separation. Otherwise, a brief comment explaining why the check is retained would help future maintainers.

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

In `@src/synthorg/api/controllers/approvals.py` around lines 503 - 505, The manual
max-length check for action_type (using QUERY_MAX_LENGTH) duplicates the
Parameter(max_length=...) validation but was intentionally retained; either add
a concise comment above the if-block explaining this (mentioning the framework
error behavior / RFC 9457 consistent response intent) or move the check into a
Litestar exception handler so observability/consistent RFC 9457 responses are
handled centrally; update the code around the action_type length check and/or
implement a Litestar exception handler that raises ApiValidationError with the
same message so the behavior remains identical while removing inline
duplication.
♻️ Duplicate comments (2)
src/synthorg/api/controllers/meetings.py (2)

90-97: ⚠️ Potential issue | 🟠 Major

Resolve analytics keys to participant names before serializing the response.

Issue #774 / the PR objective defines token_usage_by_participant as agent_name -> token_count, but the model contract and aggregation logic both expose c.agent_id. That silently changes the public response shape and exposes internal identifiers unless the issue/spec was intentionally re-baselined.

Also applies to: 121-128

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

In `@src/synthorg/api/controllers/meetings.py` around lines 90 - 97, The public
response currently exposes agent IDs instead of agent names for
token_usage_by_participant and contribution_rank; update the serialization step
that builds the meeting response (the code that constructs
token_usage_by_participant and contribution_rank) to map c.agent_id ->
agent_name using the meeting/agent lookup (e.g., resolve each agent_id to its
human-readable name from the meeting's agent registry) before assigning to the
model fields token_usage_by_participant and contribution_rank so the returned
keys/values match the spec (agent_name -> token_count and agent_name-sorted
tuple). Ensure this mapping is applied in both places where these fields are
populated (the aggregation logic that produces token_usage_by_participant and
the logic that constructs contribution_rank).

157-158: ⚠️ Potential issue | 🟠 Major

Parameter(max_length=...) can short-circuit the custom validation path.

If Litestar rejects oversized meeting_type values before the handler runs, Line 172 never executes and clients get framework validation instead of your ApiValidationError envelope. Pick one path: keep the manual RFC 9457 branch and drop the Parameter constraint, or normalize framework validation centrally and delete the branch.

#!/bin/bash
# Verify whether request-validation failures are globally normalized to RFC 9457
# and whether oversized `meeting_type` is already covered by tests.
rg -n -C2 'ApiValidationError|ValidationException|RequestValidation|RFC 9457|problem\+json|exception_handler' src/synthorg/api tests
rg -n -C2 'meeting_type exceeds maximum length|max_length=QUERY_MAX_LENGTH|QUERY_MAX_LENGTH' src/synthorg/api tests

As per coding guidelines, "src/synthorg/api/**/*.py: Errors must use RFC 9457 format in src/synthorg/api/."

Also applies to: 172-174

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

In `@src/synthorg/api/controllers/meetings.py` around lines 157 - 158, The
Parameter(max_length=QUERY_MAX_LENGTH) on the meeting_type query param prevents
your custom RFC 9457-style validation from running; remove the Parameter
constraint from the meeting_type annotation (keep the Annotated[str] | None =
None signature) and instead perform the length check inside the handler where
you raise ApiValidationError (the existing manual branch referencing
QUERY_MAX_LENGTH and ApiValidationError), or alternatively remove that manual
branch and rely on centralized framework-to-RFC9457 normalization—pick one
approach and make the code consistent (i.e., either drop Parameter and keep the
manual check+ApiValidationError in the handler, or keep Parameter and delete the
handler branch).
🤖 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/approvals.py`:
- Around line 79-97: ApprovalResponse currently exposes seconds_remaining and
urgency_level as regular fields filled by a factory; change to compute them at
access time using Pydantic's `@computed_field` to avoid mismatched instantiation.
Add an internal reference_time: datetime =
Field(default_factory=datetime.utcnow, exclude=True) on ApprovalResponse (or
accept it through the constructor), remove seconds_remaining and urgency_level
as stored fields, and implement two `@computed_field` methods named
seconds_remaining and urgency_level that compute values from reference_time and
the existing TTL/expiry data on the base ApprovalItem (use the existing
UrgencyLevel mapping logic). Ensure model_config = ConfigDict(frozen=True)
remains and the computed fields are read-only.

In `@src/synthorg/api/controllers/budget.py`:
- Around line 127-135: The model validator _validate_error_detail_consistency
currently raises ValueError when error and error_detail are inconsistent but
does not log; before each raise add a structured logger.warning call (including
the same message and contextual info such as self.error, self.error_detail and
any identifying request/id fields available on the model) so failures are
recorded, then raise the ValueError as before; reference the
_validate_error_detail_consistency method and the error/error_detail fields when
making the log entries.

In `@src/synthorg/api/controllers/meetings.py`:
- Around line 90-102: The three analytics fields on MeetingResponse
(token_usage_by_participant, contribution_rank, meeting_duration_seconds) are
derived from MeetingRecord.minutes and should be converted from stored Fields
into `@computed_field` properties on the MeetingResponse model; remove their Field
definitions and implement corresponding `@computed_field` methods that compute
values from self.minutes (reusing the same logic currently used in
_to_meeting_response()), update any serialization/consumer code to read the
computed properties, and delete the duplicate computation in
_to_meeting_response() so the schema owns the derived logic.

---

Outside diff comments:
In `@src/synthorg/api/controllers/approvals.py`:
- Around line 503-505: The manual max-length check for action_type (using
QUERY_MAX_LENGTH) duplicates the Parameter(max_length=...) validation but was
intentionally retained; either add a concise comment above the if-block
explaining this (mentioning the framework error behavior / RFC 9457 consistent
response intent) or move the check into a Litestar exception handler so
observability/consistent RFC 9457 responses are handled centrally; update the
code around the action_type length check and/or implement a Litestar exception
handler that raises ApiValidationError with the same message so the behavior
remains identical while removing inline duplication.

---

Duplicate comments:
In `@src/synthorg/api/controllers/meetings.py`:
- Around line 90-97: The public response currently exposes agent IDs instead of
agent names for token_usage_by_participant and contribution_rank; update the
serialization step that builds the meeting response (the code that constructs
token_usage_by_participant and contribution_rank) to map c.agent_id ->
agent_name using the meeting/agent lookup (e.g., resolve each agent_id to its
human-readable name from the meeting's agent registry) before assigning to the
model fields token_usage_by_participant and contribution_rank so the returned
keys/values match the spec (agent_name -> token_count and agent_name-sorted
tuple). Ensure this mapping is applied in both places where these fields are
populated (the aggregation logic that produces token_usage_by_participant and
the logic that constructs contribution_rank).
- Around line 157-158: The Parameter(max_length=QUERY_MAX_LENGTH) on the
meeting_type query param prevents your custom RFC 9457-style validation from
running; remove the Parameter constraint from the meeting_type annotation (keep
the Annotated[str] | None = None signature) and instead perform the length check
inside the handler where you raise ApiValidationError (the existing manual
branch referencing QUERY_MAX_LENGTH and ApiValidationError), or alternatively
remove that manual branch and rely on centralized framework-to-RFC9457
normalization—pick one approach and make the code consistent (i.e., either drop
Parameter and keep the manual check+ApiValidationError in the handler, or keep
Parameter and delete the handler branch).
🪄 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: 41881d5c-b8f9-4f9b-81e3-b5391fa615e9

📥 Commits

Reviewing files that changed from the base of the PR and between 2bb1958 and 730b7bc.

📒 Files selected for processing (6)
  • docs/design/communication.md
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/meetings.py
  • tests/unit/api/controllers/test_approvals.py
  • tests/unit/api/controllers/test_budget.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). (1)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (6)
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Documentation uses Markdown and is built with Zensical (config: mkdocs.yml). API reference auto-generated from OpenAPI schema via scripts/export_openapi.py. Library reference auto-generated via mkdocstrings + Griffe.

Files:

  • docs/design/communication.md
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do NOT use from __future__ import annotations - Python 3.14 has PEP 649 with native lazy annotations
Use except A, B: syntax (no parentheses) per PEP 758 - ruff enforces this on Python 3.14

Files:

  • tests/unit/api/controllers/test_approvals.py
  • src/synthorg/api/controllers/meetings.py
  • tests/unit/api/controllers/test_budget.py
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Tests must use test-provider, test-small-001, etc. instead of real vendor names
Use @pytest.mark.parametrize for testing similar cases
Mark all tests with appropriate pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Use Hypothesis for property-based testing in Python with @given and @settings decorators. Run dev profile with 1000 examples via HYPOTHESIS_PROFILE=dev environment variable.
Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks blocking indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/api/controllers/test_approvals.py
  • tests/unit/api/controllers/test_budget.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: All public functions must have type hints. Type checking enforced by mypy in strict mode.
Google-style docstrings required on all public classes and functions, enforced by ruff D rules
Use Pydantic v2 conventions: BaseModel, model_validator, computed_field, ConfigDict. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr for all identifier/name fields.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare create_task
Line length must be 88 characters (enforced by ruff)
Functions must be less than 50 lines, files less than 800 lines
Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.
Never use import logging / logging.getLogger() / print() in application code (except observability/setup.py and observability/sinks.py which may use stdlib logging and stderr print for bootstrap)
Always use logger as the variable name (not _logger, not log)
Always use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
Use structured logging: always logger.info(EVENT, key=value) - never use printf-style formatting like logger.info("msg %s", val)
All state transitions must log at INFO level
DEBUG logging is appropriate for object creation, internal flow, and entry/exit of key functions
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, etc. Vendor names only in: (1) Operations design page, (2) .claude/ skill files, (3) third-party import paths.

Files:

  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/api/**/*.py: REST API defined with Litestar. Errors must use RFC 9457 format in src/synthorg/api/
Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Files:

  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
🧠 Learnings (22)
📚 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 tests/**/*.py : 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

Applied to files:

  • tests/unit/api/controllers/test_approvals.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic. For tasks blocking indefinitely, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`.

Applied to files:

  • tests/unit/api/controllers/test_approvals.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 tests/**/*.py : Parametrize: Prefer pytest.mark.parametrize for testing similar cases.

Applied to files:

  • tests/unit/api/controllers/test_approvals.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 tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases.

Applied to files:

  • tests/unit/api/controllers/test_approvals.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to tests/**/*.py : Use `pytest.mark.parametrize` for testing similar cases

Applied to files:

  • tests/unit/api/controllers/test_approvals.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 tests/**/*.py : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.

Applied to files:

  • tests/unit/api/controllers/test_approvals.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 tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).

Applied to files:

  • tests/unit/api/controllers/test_approvals.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:

  • tests/unit/api/controllers/test_budget.py
  • src/synthorg/api/controllers/budget.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:

  • tests/unit/api/controllers/test_budget.py
  • src/synthorg/api/controllers/budget.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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/api/controllers/approvals.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 **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values 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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. Use `computed_field` for derived values instead of storing redundant fields. Use `NotBlankStr` for all identifier/name fields.

Applied to files:

  • src/synthorg/api/controllers/approvals.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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/approvals.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 **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/api/controllers/approvals.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/approvals.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: 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.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:

  • src/synthorg/api/controllers/budget.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:

  • src/synthorg/api/controllers/budget.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 : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • src/synthorg/api/controllers/budget.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 : Always log with structured kwargs: `logger.info(EVENT, key=value)` — never use old-style formatting `logger.info("msg %s", val)`

Applied to files:

  • src/synthorg/api/controllers/budget.py
🔇 Additional comments (17)
tests/unit/api/controllers/test_budget.py (2)

91-237: Strong coverage for summary semantics and pagination/filter interactions.

The new summary tests validate grouping, ordering, empty-state behavior, and “all matched records vs. current page” behavior well.


239-344: Validator tests are comprehensive for the new response contract.

The constructor validation and computed success behavior are exercised cleanly across valid and invalid combinations.

tests/unit/api/controllers/test_approvals.py (6)

424-438: LGTM!

The class is properly marked with @pytest.mark.unit and the initial test correctly verifies the presence of urgency fields in list responses.


440-449: LGTM!

Properly tests the no-expiry case where seconds_remaining is None and urgency_level is "no_expiry".


451-478: LGTM!

Good use of @pytest.mark.parametrize to consolidate similar urgency classification tests. The TTL values are safely inside their respective buckets, avoiding timing sensitivity.


480-544: LGTM!

These tests appropriately verify that all approval endpoints (create, get, approve, reject) include the new urgency fields in their responses.


546-583: LGTM!

The boundary tests now correctly freeze time using patch and are parametrized as suggested in previous reviews. The test verifies that exactly 1 hour remaining yields "high" (not "critical") and exactly 4 hours yields "normal" (not "high"), matching the < threshold semantics in the controller.


585-609: LGTM!

Correctly tests that expired approvals clamp seconds_remaining to 0.0 and show "critical" urgency. The 1-hour expiration buffer provides sufficient margin for CI timing variations.

src/synthorg/api/controllers/approvals.py (9)

62-76: LGTM!

The urgency thresholds are well-documented with clear enum values. The StrEnum choice ensures JSON serialization produces lowercase strings matching the API contract.


99-128: LGTM!

The urgency computation logic is correct: seconds are clamped to ≥ 0.0, and thresholds use strict < comparisons matching the documented boundaries (critical < 1hr, high < 4hrs, normal ≥ 4hrs).


389-414: LGTM!

Good refactoring to centralize the approval lookup and 404 handling. Properly logs at WARNING level before raising, as required by coding guidelines.


417-470: LGTM!

Good consolidation of the decision persistence logic. The function correctly uses saved (the persisted item) for both the WebSocket event and the resume intent, addressing the previous review feedback.


518-538: LGTM!

Clean refactoring using the new _get_approval_or_404 helper and response enrichment.


540-606: LGTM!

The handler correctly reuses the same now timestamp for both the expires_at calculation and response enrichment, ensuring consistency.


608-662: LGTM!

Good refactoring with consistent use of helpers. The now timestamp is captured once and used for both the decision timestamp and response enrichment.


664-718: LGTM!

The reject handler mirrors the approve handler's structure, ensuring consistent behavior and maintainability.


323-330: LGTM!

Using APPROVAL_GATE_REVIEW_TRANSITION_FAILED is more semantically accurate for this context than the generic APPROVAL_GATE_RESUME_FAILED. This improves observability by distinguishing review-gate failures from mid-execution resume failures.

Comment on lines +79 to +97
class ApprovalResponse(ApprovalItem):
"""Approval item enriched with computed urgency fields.

Attributes:
seconds_remaining: Seconds until expiry, clamped to 0.0 for
expired items (``None`` if no TTL).
urgency_level: Urgency classification based on time remaining.
"""

model_config = ConfigDict(frozen=True)

seconds_remaining: float | None = Field(
ge=0.0,
description="Seconds until expiry (null if no TTL set)",
)
urgency_level: UrgencyLevel = Field(
description="Urgency classification based on remaining time",
)

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

Consider using @computed_field for derived urgency values.

The current implementation stores seconds_remaining and urgency_level as regular fields populated by a factory function. Per coding guidelines, derived values should use @computed_field. The recommended pattern is to add an internal reference_time: datetime = Field(exclude=True) and compute the derived fields as properties. This prevents the possibility of inconsistent instantiation where someone creates an ApprovalResponse with mismatched values.

That said, the factory approach is simpler and the frozen=True config mitigates mutation risks. If the team prefers this pattern for time-dependent derived fields, consider documenting the rationale.

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

In `@src/synthorg/api/controllers/approvals.py` around lines 79 - 97,
ApprovalResponse currently exposes seconds_remaining and urgency_level as
regular fields filled by a factory; change to compute them at access time using
Pydantic's `@computed_field` to avoid mismatched instantiation. Add an internal
reference_time: datetime = Field(default_factory=datetime.utcnow, exclude=True)
on ApprovalResponse (or accept it through the constructor), remove
seconds_remaining and urgency_level as stored fields, and implement two
`@computed_field` methods named seconds_remaining and urgency_level that compute
values from reference_time and the existing TTL/expiry data on the base
ApprovalItem (use the existing UrgencyLevel mapping logic). Ensure model_config
= ConfigDict(frozen=True) remains and the computed fields are read-only.

Comment on lines +127 to +135
@model_validator(mode="after")
def _validate_error_detail_consistency(self) -> Self:
"""Ensure ``error`` and ``error_detail`` are set together."""
if self.error_detail is not None and self.error is None:
msg = "error_detail requires error to be set"
raise ValueError(msg)
if self.error is not None and self.error_detail is None:
msg = "error must be accompanied by error_detail"
raise ValueError(msg)
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

Log validator failures before raising exceptions.

Line 130 and Line 133 raise ValueError without a preceding warning/error log. Please add structured logger.warning(...) context before each raise so response-construction failures are observable.

As per coding guidelines, "Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising."

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

In `@src/synthorg/api/controllers/budget.py` around lines 127 - 135, The model
validator _validate_error_detail_consistency currently raises ValueError when
error and error_detail are inconsistent but does not log; before each raise add
a structured logger.warning call (including the same message and contextual info
such as self.error, self.error_detail and any identifying request/id fields
available on the model) so failures are recorded, then raise the ValueError as
before; reference the _validate_error_detail_consistency method and the
error/error_detail fields when making the log entries.

Comment on lines +90 to +102
token_usage_by_participant: dict[str, int] = Field(
default_factory=dict,
description="Total tokens consumed per participant",
)
contribution_rank: tuple[NotBlankStr, ...] = Field(
default=(),
description="Agent IDs sorted by contribution (descending)",
)
meeting_duration_seconds: float | None = Field(
default=None,
ge=0.0,
description="Meeting duration in seconds (null if no minutes)",
)
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
# Inspect existing computed-field patterns in the codebase before refactoring
# `MeetingResponse`.
rg -n -C2 '@computed_field|computed_field\(' src/synthorg

Repository: Aureliolo/synthorg

Length of output: 24502


🏁 Script executed:

# First, locate the meetings.py file and check its size
fd -n "meetings.py" src/synthorg/api/controllers/

# Then read the relevant lines to see the class structure
cat -n src/synthorg/api/controllers/meetings.py | head -200

Repository: Aureliolo/synthorg

Length of output: 8840


🏁 Script executed:

# Read the file without fd -n, use cat directly
cat src/synthorg/api/controllers/meetings.py | sed -n '78,137p'

Repository: Aureliolo/synthorg

Length of output: 2102


Refactor to use @computed_field for analytics instead of materializing via helper.

token_usage_by_participant, contribution_rank, and meeting_duration_seconds are pure derivations of MeetingRecord.minutes, so storing them as ordinary fields and computing in _to_meeting_response() duplicates state. Define these three fields as @computed_field properties on MeetingResponse instead—the logic stays with the schema, serialization remains consistent, and the pattern follows Pydantic v2 conventions used throughout the codebase.

Also applies to: 105-137

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

In `@src/synthorg/api/controllers/meetings.py` around lines 90 - 102, The three
analytics fields on MeetingResponse (token_usage_by_participant,
contribution_rank, meeting_duration_seconds) are derived from
MeetingRecord.minutes and should be converted from stored Fields into
`@computed_field` properties on the MeetingResponse model; remove their Field
definitions and implement corresponding `@computed_field` methods that compute
values from self.minutes (reusing the same logic currently used in
_to_meeting_response()), update any serialization/consumer code to read the
computed properties, and delete the duplicate computation in
_to_meeting_response() so the schema owns the derived logic.

@Aureliolo Aureliolo force-pushed the feat/approval-meeting-api-extensions branch from 730b7bc to adf7f94 Compare March 26, 2026 14:44
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 26, 2026 14:45 — 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: 2

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/budget.py (1)

217-255: ⚠️ Potential issue | 🟠 Major

Add manual query-parameter length checks to match the fallback pattern in approvals.py and meetings.py.

Both agent_id and task_id use Parameter(max_length=QUERY_MAX_LENGTH), but Litestar's validation crashes the worker instead of returning a proper RFC 9457 error response. Add handler-level guards before calling get_records(...):

Example pattern
if agent_id is not None and len(agent_id) > QUERY_MAX_LENGTH:
    msg = f"agent_id exceeds maximum length of {QUERY_MAX_LENGTH}"
    raise ApiValidationError(msg)
if task_id is not None and len(task_id) > QUERY_MAX_LENGTH:
    msg = f"task_id exceeds maximum length of {QUERY_MAX_LENGTH}"
    raise ApiValidationError(msg)

(See src/synthorg/api/controllers/approvals.py line 506 and src/synthorg/api/controllers/meetings.py line 175 for the same pattern.)

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

In `@src/synthorg/api/controllers/budget.py` around lines 217 - 255, Add explicit
length guards for the query params agent_id and task_id before calling
AppState.cost_tracker.get_records: check if each is not None and len(value) >
QUERY_MAX_LENGTH and raise ApiValidationError with a clear message like
"agent_id exceeds maximum length of {QUERY_MAX_LENGTH}" (and similarly for
task_id). Place these checks in the same handler function that calls get_records
so Litestar doesn't crash and the API returns the proper RFC 9457 validation
error.
♻️ Duplicate comments (4)
src/synthorg/api/controllers/meetings.py (2)

78-102: 🛠️ Refactor suggestion | 🟠 Major

Make token_usage_by_participant, contribution_rank, and meeting_duration_seconds computed fields.

These values are derived entirely from MeetingRecord.minutes, but the DTO stores them as normal fields and depends on _to_meeting_response() to keep them synchronized. That leaves room for inconsistent MeetingResponse instances and makes every endpoint remember a bespoke enrichment step. Move the derivation onto MeetingResponse itself and let the mapper just wrap the base record.

Based on learnings, "use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields."

Also applies to: 105-137

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

In `@src/synthorg/api/controllers/meetings.py` around lines 78 - 102,
MeetingResponse currently stores token_usage_by_participant, contribution_rank,
and meeting_duration_seconds as regular fields and relies on the mapper
_to_meeting_response() to populate them; change these to Pydantic computed
fields on MeetingResponse so they are derived from MeetingRecord.minutes at
access time (use pydantic.computed_field or the v2 equivalent) and remove the
redundant Field declarations/defaults; keep the same return types
(dict[str,int], tuple[NotBlankStr,...], float|None) and descriptions in
docstring/comments, update model_config if needed for computed fields, and
simplify the mapper to only wrap the base MeetingRecord into MeetingResponse
without performing enrichment.

19-20: ⚠️ Potential issue | 🟡 Minor

Log the manual meeting_type rejection before raising.

This custom branch exists specifically because you're bypassing framework validation here, but it currently raises ApiValidationError without the WARNING/ERROR event required for explicit error paths. Add a structured API_VALIDATION_FAILED log with the field name and actual length before raising.

🛠️ Minimal fix
-from synthorg.observability.events.api import API_MEETING_TRIGGERED
+from synthorg.observability.events.api import (
+    API_MEETING_TRIGGERED,
+    API_VALIDATION_FAILED,
+)
         if meeting_type is not None and len(meeting_type) > QUERY_MAX_LENGTH:
+            logger.warning(
+                API_VALIDATION_FAILED,
+                field="meeting_type",
+                max_length=QUERY_MAX_LENGTH,
+                actual_length=len(meeting_type),
+            )
             msg = f"meeting_type exceeds maximum length of {QUERY_MAX_LENGTH}"
             raise ApiValidationError(msg)

As per coding guidelines, "Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising."

Also applies to: 172-177

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

In `@src/synthorg/api/controllers/meetings.py` around lines 19 - 20, In the branch
in meetings.py that manually rejects the meeting_type (the code that currently
raises ApiValidationError), add a structured log call using the
API_VALIDATION_FAILED event before raising: include the field name
("meeting_type") and the actual length/value (e.g., len(meeting_type) and
meeting_type) in the log payload at WARNING/ERROR level; do the same change for
the similar branch around lines 172-177. Locate the branch by searching for the
ApiValidationError raises related to meeting_type and insert a processLogger (or
existing logger) API_VALIDATION_FAILED event with context (field, actual_length,
max_length) immediately prior to raising the exception.
src/synthorg/api/controllers/budget.py (1)

127-135: ⚠️ Potential issue | 🟠 Major

Log invalid CostRecordListResponse states before raising.

Both validator branches raise ValueError silently, so malformed response construction loses the context that would explain why serialization failed.

As per coding guidelines, "Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising."

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

In `@src/synthorg/api/controllers/budget.py` around lines 127 - 135, The validator
method _validate_error_detail_consistency currently raises ValueError without
logging; update this method to log the invalid CostRecordListResponse state
(include which branch triggered, the error message, and relevant context such as
self.error and self.error_detail or other identifying fields) at WARNING or
ERROR level immediately before raising to preserve diagnostic context; use the
module/class logger (e.g., logger or getLogger(__name__)) and ensure the log
message is descriptive and includes the same msg string used for the ValueError
so callers can correlate logs with the raised exception.
src/synthorg/api/controllers/approvals.py (1)

79-97: 🛠️ Refactor suggestion | 🟠 Major

Expose urgency via @computed_field, not stored fields.

seconds_remaining and urgency_level are derived from expires_at plus the reference time, so keeping them as regular fields still allows inconsistent ApprovalResponse instances outside _to_approval_response(). An excluded backing reference_time with @computed_field keeps the DTO internally consistent and matches the rest of the Pydantic guidance in this repo.

For Pydantic v2.12.5 response models, what is the recommended pattern for exposing derived read-only fields: regular fields populated by a factory, or `@computed_field` backed by an excluded input field?

As per coding guidelines, "Use Pydantic v2 conventions: BaseModel, model_validator, computed_field, ConfigDict. Use @computed_field for derived values instead of storing redundant fields."

Also applies to: 99-128

🤖 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/approvals.py`:
- Around line 503-508: Add a warning log immediately before raising
ApiValidationError in the action_type length check: log the rejected value
(action_type), its length (len(action_type)), and the limit (QUERY_MAX_LENGTH)
at WARNING level so the event is observable, e.g., use the module/controller
logger (logger.warning(...)) in the guard that checks "if action_type is not
None and len(action_type) > QUERY_MAX_LENGTH" before raising
ApiValidationError(msg).

In `@tests/unit/api/controllers/test_meetings.py`:
- Around line 283-299: The test test_trigger_response_includes_analytics
currently only checks for presence of keys, which allows regressions; update it
to assert the concrete analytics values returned when
mock_scheduler.trigger_event returns (_make_record("mtg-triggered"),). After
calling meeting_client.post(...), extract item = resp.json()["data"][0] and
assert specific fields equal the deterministic values produced by
_make_record("mtg-triggered") (e.g., compare token_usage_by_participant,
contribution_rank, and meeting_duration_seconds to their expected numeric/dict
values), ensuring the test fails if defaults or wrong durations are returned;
keep references to mock_scheduler.trigger_event and _make_record to locate where
expected values come from.

---

Outside diff comments:
In `@src/synthorg/api/controllers/budget.py`:
- Around line 217-255: Add explicit length guards for the query params agent_id
and task_id before calling AppState.cost_tracker.get_records: check if each is
not None and len(value) > QUERY_MAX_LENGTH and raise ApiValidationError with a
clear message like "agent_id exceeds maximum length of {QUERY_MAX_LENGTH}" (and
similarly for task_id). Place these checks in the same handler function that
calls get_records so Litestar doesn't crash and the API returns the proper RFC
9457 validation error.

---

Duplicate comments:
In `@src/synthorg/api/controllers/budget.py`:
- Around line 127-135: The validator method _validate_error_detail_consistency
currently raises ValueError without logging; update this method to log the
invalid CostRecordListResponse state (include which branch triggered, the error
message, and relevant context such as self.error and self.error_detail or other
identifying fields) at WARNING or ERROR level immediately before raising to
preserve diagnostic context; use the module/class logger (e.g., logger or
getLogger(__name__)) and ensure the log message is descriptive and includes the
same msg string used for the ValueError so callers can correlate logs with the
raised exception.

In `@src/synthorg/api/controllers/meetings.py`:
- Around line 78-102: MeetingResponse currently stores
token_usage_by_participant, contribution_rank, and meeting_duration_seconds as
regular fields and relies on the mapper _to_meeting_response() to populate them;
change these to Pydantic computed fields on MeetingResponse so they are derived
from MeetingRecord.minutes at access time (use pydantic.computed_field or the v2
equivalent) and remove the redundant Field declarations/defaults; keep the same
return types (dict[str,int], tuple[NotBlankStr,...], float|None) and
descriptions in docstring/comments, update model_config if needed for computed
fields, and simplify the mapper to only wrap the base MeetingRecord into
MeetingResponse without performing enrichment.
- Around line 19-20: In the branch in meetings.py that manually rejects the
meeting_type (the code that currently raises ApiValidationError), add a
structured log call using the API_VALIDATION_FAILED event before raising:
include the field name ("meeting_type") and the actual length/value (e.g.,
len(meeting_type) and meeting_type) in the log payload at WARNING/ERROR level;
do the same change for the similar branch around lines 172-177. Locate the
branch by searching for the ApiValidationError raises related to meeting_type
and insert a processLogger (or existing logger) API_VALIDATION_FAILED event with
context (field, actual_length, max_length) immediately prior to raising the
exception.
🪄 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: 4277b283-bdd9-48fd-932f-c5a439f3aec0

📥 Commits

Reviewing files that changed from the base of the PR and between 730b7bc and adf7f94.

📒 Files selected for processing (14)
  • docs/design/communication.md
  • docs/design/operations.md
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/observability/events/approval_gate.py
  • tests/unit/api/controllers/test_approvals.py
  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_meetings.py
  • web/src/api/endpoints/approvals.ts
  • web/src/api/endpoints/budget.ts
  • web/src/api/endpoints/meetings.ts
  • 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). (6)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do NOT use from __future__ import annotations - Python 3.14 has PEP 649 with native lazy annotations
Use except A, B: syntax (no parentheses) per PEP 758 - ruff enforces this on Python 3.14

Files:

  • src/synthorg/observability/events/approval_gate.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/api/controllers/meetings.py
  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_approvals.py
  • tests/unit/api/controllers/test_meetings.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: All public functions must have type hints. Type checking enforced by mypy in strict mode.
Google-style docstrings required on all public classes and functions, enforced by ruff D rules
Use Pydantic v2 conventions: BaseModel, model_validator, computed_field, ConfigDict. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr for all identifier/name fields.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare create_task
Line length must be 88 characters (enforced by ruff)
Functions must be less than 50 lines, files less than 800 lines
Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.
Never use import logging / logging.getLogger() / print() in application code (except observability/setup.py and observability/sinks.py which may use stdlib logging and stderr print for bootstrap)
Always use logger as the variable name (not _logger, not log)
Always use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
Use structured logging: always logger.info(EVENT, key=value) - never use printf-style formatting like logger.info("msg %s", val)
All state transitions must log at INFO level
DEBUG logging is appropriate for object creation, internal flow, and entry/exit of key functions
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, etc. Vendor names only in: (1) Operations design page, (2) .claude/ skill files, (3) third-party import paths.

Files:

  • src/synthorg/observability/events/approval_gate.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • src/synthorg/observability/events/approval_gate.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Documentation uses Markdown and is built with Zensical (config: mkdocs.yml). API reference auto-generated from OpenAPI schema via scripts/export_openapi.py. Library reference auto-generated via mkdocstrings + Griffe.

Files:

  • docs/design/communication.md
  • docs/design/operations.md
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/api/**/*.py: REST API defined with Litestar. Errors must use RFC 9457 format in src/synthorg/api/
Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Files:

  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Tests must use test-provider, test-small-001, etc. instead of real vendor names
Use @pytest.mark.parametrize for testing similar cases
Mark all tests with appropriate pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Use Hypothesis for property-based testing in Python with @given and @settings decorators. Run dev profile with 1000 examples via HYPOTHESIS_PROFILE=dev environment variable.
Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks blocking indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_approvals.py
  • tests/unit/api/controllers/test_meetings.py
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{ts,tsx}: React 19 + shadcn/ui + Tailwind CSS for web dashboard. TypeScript required in web/src/.
Run ESLint with npm --prefix web run lint for React code style and quality checks
Run TypeScript type checking with npm --prefix web run type-check
Use Axios for HTTP requests in the web dashboard
Use @tanstack/react-query for server state management and caching in the web dashboard

Files:

  • web/src/api/types.ts
  • web/src/api/endpoints/budget.ts
  • web/src/api/endpoints/approvals.ts
  • web/src/api/endpoints/meetings.ts
web/src/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Web dashboard API client uses Axios with endpoint modules (18 domains) in web/src/api/

Files:

  • web/src/api/types.ts
  • web/src/api/endpoints/budget.ts
  • web/src/api/endpoints/approvals.ts
  • web/src/api/endpoints/meetings.ts
🧠 Learnings (45)
📚 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/api/**/*.py : Authentication uses JWT + API key. Approval gate integration for high-risk operations.

Applied to files:

  • src/synthorg/observability/events/approval_gate.py
  • docs/design/operations.md
  • src/synthorg/api/controllers/approvals.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 : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/api.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/observability/events/api.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/observability/events/api.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:

  • src/synthorg/observability/events/api.py
  • src/synthorg/api/controllers/budget.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 event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/api.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/observability/events/api.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/api.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 event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/api.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 : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/api.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/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/observability/events/api.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: 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:

  • docs/design/operations.md
  • web/src/api/endpoints/budget.ts
  • src/synthorg/api/controllers/budget.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:

  • docs/design/operations.md
  • tests/unit/api/controllers/test_budget.py
  • src/synthorg/api/controllers/budget.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:

  • docs/design/operations.md
  • tests/unit/api/controllers/test_budget.py
  • web/src/api/endpoints/budget.ts
  • src/synthorg/api/controllers/budget.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:

  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/approvals.py
📚 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 tests/**/*.py : 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

Applied to files:

  • tests/unit/api/controllers/test_approvals.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic. For tasks blocking indefinitely, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`.

Applied to files:

  • tests/unit/api/controllers/test_approvals.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 tests/**/*.py : Parametrize: Prefer pytest.mark.parametrize for testing similar cases.

Applied to files:

  • tests/unit/api/controllers/test_approvals.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to tests/**/*.py : Use `pytest.mark.parametrize` for testing similar cases

Applied to files:

  • tests/unit/api/controllers/test_approvals.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 tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases.

Applied to files:

  • tests/unit/api/controllers/test_approvals.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 tests/**/*.py : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.

Applied to files:

  • tests/unit/api/controllers/test_approvals.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 tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).

Applied to files:

  • tests/unit/api/controllers/test_approvals.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/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • tests/unit/api/controllers/test_meetings.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:

  • tests/unit/api/controllers/test_meetings.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:

  • tests/unit/api/controllers/test_meetings.py
  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/synthorg/api/**/*.py : Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Applied to files:

  • tests/unit/api/controllers/test_meetings.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:

  • src/synthorg/api/controllers/budget.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 : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • src/synthorg/api/controllers/budget.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 : Always log with structured kwargs: `logger.info(EVENT, key=value)` — never use old-style formatting `logger.info("msg %s", val)`

Applied to files:

  • src/synthorg/api/controllers/budget.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/budget.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/**/*.py : All error paths must log at WARNING or ERROR with context before raising

Applied to files:

  • src/synthorg/api/controllers/budget.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.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 : 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:

  • src/synthorg/api/controllers/budget.py
📚 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:

  • src/synthorg/api/controllers/budget.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 : 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:

  • src/synthorg/api/controllers/budget.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 : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/budget.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/budget.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/approvals.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/api/controllers/approvals.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 **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values 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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. Use `computed_field` for derived values instead of storing redundant fields. Use `NotBlankStr` for all identifier/name fields.

Applied to files:

  • src/synthorg/api/controllers/approvals.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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/approvals.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 **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.

Applied to files:

  • src/synthorg/api/controllers/approvals.py
🪛 markdownlint-cli2 (0.22.0)
docs/design/operations.md

[warning] 1008-1008: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

Aureliolo and others added 2 commits March 26, 2026 16:16
Add computed fields to API responses for richer dashboard displays:

- Approval responses: seconds_remaining, urgency_level enum
  (critical/high/normal/no_expiry) on all endpoints
- Meeting responses: token_usage_by_participant, contribution_rank,
  meeting_duration_seconds on list/get/trigger endpoints
- Budget records: daily_summary aggregation and period_summary stats
  (avg cost, total tokens) computed from all filtered records

Closes #774

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PeriodSummary.avg_cost_usd: convert stored field to @computed_field
- CostRecordListResponse: add error_detail consistency validator
- DailySummary.date: use NotBlankStr instead of raw str
- ApprovalResponse.seconds_remaining: add ge=0.0 constraint
- MeetingResponse.meeting_duration_seconds: add ge=0.0 constraint
- Fix _try_review_gate_transition using wrong log event constant
- Add 10 new tests: boundary, edge cases, missing assertions
- Update frontend types: ApprovalResponse, MeetingResponse, summaries
- Update design spec: approval urgency, meeting analytics, budget summaries

Pre-reviewed by 9 agents, 17 findings addressed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aureliolo and others added 4 commits March 26, 2026 16:16
…ernal reviewers)

- Critical: budget.ts error_detail preserved in ApiRequestError (RFC 9457)
- Major: _to_approval_response accepts `now` param for deterministic time
- Major: boundary tests freeze time via datetime mock (no timing margins)
- Major: approve/reject extracted into _get_approval_or_404 and
  _save_decision_and_notify helpers (under 50-line limit)
- Major: contribution_rank uses tuple[NotBlankStr, ...] per convention
- Major: communication.md type corrected and duration separated from
  contribution-derived fields
- Major: CostRecordListResponse validator tested (4 new tests)
- Medium: math.fsum for floating-point cost summation precision
- Medium: .date().isoformat() replaces strftime for date keys
- Medium: logging added to budget controller and meetings trigger/validation
- Medium: filtered-summary test verifies aggregation is post-filter
- Medium: daily summary sort order explicitly asserted
- Medium: __import__ antipattern replaced with normal import in test fixtures
- Medium: duplicated test client setup extracted into helper
- Minor: docstrings improved (thresholds, clamping, duration, aggregation,
  error_detail pairing, PeriodSummary field order)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces hardcoded 128 in Parameter(max_length=128) annotations with
the QUERY_MAX_LENGTH constant across approvals, budget, and meetings
controllers, preventing drift between framework-level and controller-
level validation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use saved (post-persistence) instead of updated (pre-persistence) when
publishing WebSocket events, ensuring the event reflects committed state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Clamp meeting_duration_seconds to 0.0 for negative deltas; update docs
- Parametrize urgency level tests and boundary tests with pytest.mark
- Restore manual query param validation (Parameter max_length crashes
  for query params instead of returning proper HTTP errors)
- Budget records logging raised from DEBUG to INFO
- Add token total assertions to period summary and filtered summary tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aureliolo and others added 2 commits March 26, 2026 16:16
Litestar Parameter(max_length=...) on query params crashes the worker
instead of returning a proper RFC 9457 error response. Document why
the manual checks are retained alongside the Parameter annotation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ssertions

- Log API_VALIDATION_FAILED before raising in approvals, meetings, and
  budget controllers for query param length violations
- Add manual length guards for budget agent_id/task_id query params
  (same Litestar crash issue as approvals/meetings)
- Assert concrete analytics values in trigger_meeting test instead of
  just checking key presence

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 26, 2026 15:17 — 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: 1

♻️ Duplicate comments (3)
src/synthorg/api/controllers/budget.py (1)

131-140: ⚠️ Potential issue | 🟡 Minor

Add logging before raising in the model validator.

The validator raises ValueError at lines 136 and 139 without a preceding log statement. While this is an internal consistency check, the coding guidelines require logging all error paths before raising.

,

🛡️ Proposed fix to add logging
     `@model_validator`(mode="after")
     def _validate_error_detail_consistency(self) -> Self:
         """Ensure ``error`` and ``error_detail`` are set together."""
         if self.error_detail is not None and self.error is None:
             msg = "error_detail requires error to be set"
+            logger.warning(
+                API_VALIDATION_FAILED,
+                reason=msg,
+                error=self.error,
+                error_detail_present=self.error_detail is not None,
+            )
             raise ValueError(msg)
         if self.error is not None and self.error_detail is None:
             msg = "error must be accompanied by error_detail"
+            logger.warning(
+                API_VALIDATION_FAILED,
+                reason=msg,
+                error=self.error,
+                error_detail_present=self.error_detail is not None,
+            )
             raise ValueError(msg)
         return self

As per coding guidelines, "All error paths must log at WARNING or ERROR with context before raising."

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

In `@src/synthorg/api/controllers/budget.py` around lines 131 - 140, The model
validator _validate_error_detail_consistency currently raises ValueError in two
branches without logging; before each raise add a logging call (logger.error or
logger.warning) that includes the same descriptive message and contextual fields
(e.g., current self.error and self.error_detail) so the error path is logged
prior to raising; ensure you use the module's standard logger (logger or
logging.getLogger(__name__)) and log both branches ("error_detail requires error
to be set" and "error must be accompanied by error_detail") with context.
src/synthorg/api/controllers/meetings.py (2)

93-105: 🛠️ Refactor suggestion | 🟠 Major

Use @computed_field here, or make this a dedicated view model instead.

MeetingResponse.token_usage_by_participant, MeetingResponse.contribution_rank, and MeetingResponse.meeting_duration_seconds are stored as ordinary fields even though the implementation derives them in _to_meeting_response(). That duplicates state on top of MeetingRecord; if the final payload really needs extra external data, this should be a separate response model rather than MeetingRecord plus helper-populated redundant fields.

Based on learnings, "Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields."

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

In `@src/synthorg/api/controllers/meetings.py` around lines 93 - 105, The
MeetingResponse model currently defines token_usage_by_participant,
contribution_rank, and meeting_duration_seconds as ordinary validated fields
while their values are derived in _to_meeting_response(), causing redundant
state; update MeetingResponse to declare these as Pydantic v2 computed fields
using `@computed_field` on methods that return token_usage_by_participant,
contribution_rank, and meeting_duration_seconds (or alternatively create a
separate view model for the final payload and move these derived properties
there), and remove their default Field(...) declarations so the model no longer
duplicates MeetingRecord state.

124-139: ⚠️ Potential issue | 🟠 Major

The analytics still don't match the public response contract.

The PR objective says token_usage_by_participant is keyed by participant name and meeting_duration_seconds is only for completed meetings. This block still keys analytics by MeetingContribution.agent_id and emits them whenever record.minutes exists, so a non-completed record with retained minutes would leak ID-based analytics that don't match the documented response. Gate this on record.status == MeetingStatus.COMPLETED and resolve the outward keys to participant names here, or update the API/docs/types/tests to explicitly contract on IDs.

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

In `@src/synthorg/api/controllers/meetings.py` around lines 124 - 139, The
analytics must be emitted only for completed meetings and keyed by participant
name, not agent ID: change the block that computes usage/rank/duration to run
only when record.status == MeetingStatus.COMPLETED and record.minutes is
present; build a lookup from participant IDs to names (e.g. from
record.participants) and aggregate token usage into usage_by_name using that map
(fallback to ID if name missing), compute contribution_rank from that name-keyed
map, and set meeting_duration_seconds only for completed meetings (otherwise
return None or 0 per contract) before constructing MeetingResponse with
token_usage_by_participant, contribution_rank, and meeting_duration_seconds.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/api/controllers/test_meetings.py`:
- Around line 69-124: The test fixture _make_minutes currently sets
participant_ids=("participant-1",) while with_contributions=True creates
MeetingContribution entries for "agent-alpha" and "agent-beta", producing
inconsistent test data; update _make_minutes so participant_ids includes the
contributors (e.g., ("agent-alpha","agent-beta")) whenever with_contributions is
true (or adjust the contributions to match the participant_ids), and then update
the related assertions expecting analytics for agent-alpha and agent-beta;
ensure changes are applied to the other similar fixture usages mentioned (the
block around lines 365-384) so MeetingMinutes, MeetingContribution,
participant_ids, total_input_tokens and total_output_tokens remain consistent.

---

Duplicate comments:
In `@src/synthorg/api/controllers/budget.py`:
- Around line 131-140: The model validator _validate_error_detail_consistency
currently raises ValueError in two branches without logging; before each raise
add a logging call (logger.error or logger.warning) that includes the same
descriptive message and contextual fields (e.g., current self.error and
self.error_detail) so the error path is logged prior to raising; ensure you use
the module's standard logger (logger or logging.getLogger(__name__)) and log
both branches ("error_detail requires error to be set" and "error must be
accompanied by error_detail") with context.

In `@src/synthorg/api/controllers/meetings.py`:
- Around line 93-105: The MeetingResponse model currently defines
token_usage_by_participant, contribution_rank, and meeting_duration_seconds as
ordinary validated fields while their values are derived in
_to_meeting_response(), causing redundant state; update MeetingResponse to
declare these as Pydantic v2 computed fields using `@computed_field` on methods
that return token_usage_by_participant, contribution_rank, and
meeting_duration_seconds (or alternatively create a separate view model for the
final payload and move these derived properties there), and remove their default
Field(...) declarations so the model no longer duplicates MeetingRecord state.
- Around line 124-139: The analytics must be emitted only for completed meetings
and keyed by participant name, not agent ID: change the block that computes
usage/rank/duration to run only when record.status == MeetingStatus.COMPLETED
and record.minutes is present; build a lookup from participant IDs to names
(e.g. from record.participants) and aggregate token usage into usage_by_name
using that map (fallback to ID if name missing), compute contribution_rank from
that name-keyed map, and set meeting_duration_seconds only for completed
meetings (otherwise return None or 0 per contract) before constructing
MeetingResponse with token_usage_by_participant, contribution_rank, and
meeting_duration_seconds.
🪄 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: 9006abc9-9c8b-4f6a-9768-1b4c48009cb0

📥 Commits

Reviewing files that changed from the base of the PR and between adf7f94 and 25d5247.

📒 Files selected for processing (4)
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/meetings.py
  • tests/unit/api/controllers/test_meetings.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). (1)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do NOT use from __future__ import annotations - Python 3.14 has PEP 649 with native lazy annotations
Use except A, B: syntax (no parentheses) per PEP 758 - ruff enforces this on Python 3.14

Files:

  • src/synthorg/api/controllers/meetings.py
  • tests/unit/api/controllers/test_meetings.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: All public functions must have type hints. Type checking enforced by mypy in strict mode.
Google-style docstrings required on all public classes and functions, enforced by ruff D rules
Use Pydantic v2 conventions: BaseModel, model_validator, computed_field, ConfigDict. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr for all identifier/name fields.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) over bare create_task
Line length must be 88 characters (enforced by ruff)
Functions must be less than 50 lines, files less than 800 lines
Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.
Never use import logging / logging.getLogger() / print() in application code (except observability/setup.py and observability/sinks.py which may use stdlib logging and stderr print for bootstrap)
Always use logger as the variable name (not _logger, not log)
Always use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
Use structured logging: always logger.info(EVENT, key=value) - never use printf-style formatting like logger.info("msg %s", val)
All state transitions must log at INFO level
DEBUG logging is appropriate for object creation, internal flow, and entry/exit of key functions
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, etc. Vendor names only in: (1) Operations design page, (2) .claude/ skill files, (3) third-party import paths.

Files:

  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/api/**/*.py: REST API defined with Litestar. Errors must use RFC 9457 format in src/synthorg/api/
Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Files:

  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Tests must use test-provider, test-small-001, etc. instead of real vendor names
Use @pytest.mark.parametrize for testing similar cases
Mark all tests with appropriate pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Use Hypothesis for property-based testing in Python with @given and @settings decorators. Run dev profile with 1000 examples via HYPOTHESIS_PROFILE=dev environment variable.
Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks blocking indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/api/controllers/test_meetings.py
🧠 Learnings (28)
📚 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:

  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/approvals.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/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • tests/unit/api/controllers/test_meetings.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:

  • tests/unit/api/controllers/test_meetings.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:

  • tests/unit/api/controllers/test_meetings.py
  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/synthorg/api/**/*.py : Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management

Applied to files:

  • tests/unit/api/controllers/test_meetings.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.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/controllers/budget.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: 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.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:

  • src/synthorg/api/controllers/budget.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:

  • src/synthorg/api/controllers/budget.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 : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • src/synthorg/api/controllers/budget.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.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/**/*.py : All error paths must log at WARNING or ERROR with context before raising

Applied to files:

  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.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.py
  • src/synthorg/api/controllers/approvals.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 : 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:

  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
📚 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:

  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.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 : 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:

  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.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 : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/budget.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/api/**/*.py : Authentication uses JWT + API key. Approval gate integration for high-risk operations.

Applied to files:

  • src/synthorg/api/controllers/approvals.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/approvals.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/api/controllers/approvals.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 **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values 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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-26T13:22:36.844Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T13:22:36.844Z
Learning: Applies to src/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. Use `computed_field` for derived values instead of storing redundant fields. Use `NotBlankStr` for all identifier/name fields.

Applied to files:

  • src/synthorg/api/controllers/approvals.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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/approvals.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 **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.

Applied to files:

  • src/synthorg/api/controllers/approvals.py
🔇 Additional comments (20)
src/synthorg/api/controllers/budget.py (7)

3-31: LGTM!

Imports are well-organized: standard library, framework, then internal modules. Observability setup follows project conventions with event constants and structured logger instantiation.


48-72: LGTM!

DailySummary correctly uses ConfigDict(frozen=True) for immutability, NotBlankStr for the date identifier, and appropriate ge=0 constraints for numeric fields.


74-105: LGTM!

PeriodSummary properly uses @computed_field for derived avg_cost_usd rather than storing redundant data. Division-by-zero is correctly guarded.


149-191: LGTM!

_build_summaries correctly handles the empty-records edge case, uses math.fsum for accurate floating-point summation, and returns chronologically sorted daily summaries. Field access (cost_usd, timestamp, input_tokens, output_tokens) aligns with CostRecord model definition.


217-240: LGTM!

Endpoint signature and docstring are well-defined. The return type correctly reflects the enriched response, and the docstring clarifies that summaries span all matching records, not just the current page.


241-253: LGTM!

Validation correctly logs API_VALIDATION_FAILED at WARNING level with structured context before raising ApiValidationError. The inline comment documenting the Litestar limitation is helpful for future maintainers.


255-273: LGTM!

The implementation correctly computes summaries from all filtered records before pagination, ensuring the response reflects complete aggregations regardless of page size. Logging at INFO level with structured kwargs aligns with the observability contract.

src/synthorg/api/controllers/approvals.py (13)

63-64: LGTM!

The urgency threshold constants are clearly named and appropriately scoped as module-private. The values align with the spec (1 hour = 3600s for critical, 4 hours = 14400s for high).


67-78: LGTM!

UrgencyLevel enum correctly implements the specified thresholds with clear documentation. Using StrEnum ensures proper JSON serialization for API responses.


80-98: LGTM!

The ApprovalResponse DTO properly extends ApprovalItem with urgency enrichment fields. The frozen=True config ensures immutability, and the ge=0.0 constraint on seconds_remaining enforces the clamping invariant at the type level.


100-130: LGTM!

The conversion function correctly:

  • Handles None expiry as NO_EXPIRY
  • Clamps negative remaining time to 0.0 for expired items
  • Applies urgency thresholds in the correct order (critical < high < normal)
  • Uses model_dump() to spread base fields cleanly

186-188: LGTM!

Exception handling uses the except A, B: syntax per PEP 758 guidelines for Python 3.14, correctly re-raising critical errors while catching others for logging.


390-416: LGTM!

The _get_approval_or_404 helper properly centralizes the lookup-and-404 pattern with correct observability (warning log with context before raising). This eliminates duplication across get_approval, approve, and reject endpoints.


418-472: LGTM!

The _save_decision_and_notify helper cleanly consolidates the decision persistence workflow:

  • Atomic save_if_pending with conflict detection
  • Uses saved (the persisted item) for event publishing (addressing prior feedback)
  • Proper logging before raising ConflictError
  • Resume intent signaling with the committed state

504-526: LGTM!

The manual validation is retained with clear documentation of the Litestar workaround rationale. Using a single now timestamp for all items in the list ensures consistent urgency calculations within the response, which is the correct approach for list endpoints.


528-548: LGTM!

The get_approval endpoint correctly uses the centralized _get_approval_or_404 helper and enriches the response with urgency fields.


550-616: LGTM!

The create_approval endpoint correctly uses the same now timestamp for both the created_at field and the response enrichment, ensuring temporal consistency in the returned urgency values.


618-672: LGTM!

The approve endpoint correctly leverages the centralized helpers and maintains timestamp consistency between the decision timestamp and response enrichment.


674-728: LGTM!

The reject endpoint follows the same well-structured pattern as approve, correctly using the centralized helpers and maintaining timestamp consistency.


324-331: LGTM!

The updated event constant APPROVAL_GATE_REVIEW_TRANSITION_FAILED aligns with the domain-specific event naming convention and provides better observability categorization.

_make_minutes now sets participant_ids to ("agent-alpha", "agent-beta")
when with_contributions=True, matching the actual contributor agent IDs
instead of using a mismatched ("participant-1",).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo force-pushed the feat/approval-meeting-api-extensions branch from 25d5247 to 593d8e8 Compare March 26, 2026 15:22
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 26, 2026 15:23 — 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: 1

♻️ Duplicate comments (2)
web/src/api/endpoints/budget.ts (1)

36-47: ⚠️ Potential issue | 🟡 Minor

The success path still trusts the payload shape.

This fixes error_detail, but Line 42 through Line 47 still assume data, pagination, daily_summary, and period_summary exist whenever success is truthy. A malformed or version-skewed success body will now throw a TypeError instead of the usual ApiRequestError.

🛡️ Minimal guard
   const response = await apiClient.get<CostRecordListResponseBody>('/budget/records', { params })
   const body = response.data
-  if (!body?.success) {
-    throw new ApiRequestError(body?.error ?? 'Unknown API error', body?.error_detail ?? null)
+  if (!body || typeof body !== 'object') {
+    throw new ApiRequestError('Unknown API error')
+  }
+  if (!body.success) {
+    throw new ApiRequestError(body.error ?? 'Unknown API error', body.error_detail ?? null)
+  }
+  if (
+    !Array.isArray(body.data) ||
+    !body.pagination ||
+    typeof body.pagination.total !== 'number' ||
+    typeof body.pagination.offset !== 'number' ||
+    typeof body.pagination.limit !== 'number' ||
+    !Array.isArray(body.daily_summary) ||
+    !body.period_summary
+  ) {
+    throw new ApiRequestError('Unexpected API response format')
   }
   return {
     data: body.data,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/api/endpoints/budget.ts` around lines 36 - 47, The success branch
trusts the returned body shape and can throw a TypeError if fields are missing;
guard the response before returning by validating required properties on body
(e.g., data, pagination.total/offset/limit, daily_summary, period_summary) after
apiClient.get<CostRecordListResponseBody> and before the return, and if any
required field is absent or malformed throw an ApiRequestError with a clear
message (use body?.error_detail if present); update the logic around the body
variable and the return block to perform these checks so malformed success
payloads produce ApiRequestError instead of runtime TypeError.
src/synthorg/api/controllers/budget.py (1)

131-139: ⚠️ Potential issue | 🟠 Major

Validator failures still raise without a warning/error log.

Line 134 and Line 137 are explicit error paths, but they still raise ValueError silently. That makes malformed CostRecordListResponse construction invisible in the normal observability stream.

🛡️ Minimal fix
     `@model_validator`(mode="after")
     def _validate_error_detail_consistency(self) -> Self:
         """Ensure ``error`` and ``error_detail`` are set together."""
         if self.error_detail is not None and self.error is None:
             msg = "error_detail requires error to be set"
+            logger.warning(
+                API_VALIDATION_FAILED,
+                message=msg,
+                error_present=self.error is not None,
+                error_detail_present=self.error_detail is not None,
+            )
             raise ValueError(msg)
         if self.error is not None and self.error_detail is None:
             msg = "error must be accompanied by error_detail"
+            logger.warning(
+                API_VALIDATION_FAILED,
+                message=msg,
+                error_present=self.error is not None,
+                error_detail_present=self.error_detail is not None,
+            )
             raise ValueError(msg)
         return self

As per coding guidelines, "All error paths must log at WARNING or ERROR with context before raising."

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

In `@src/synthorg/api/controllers/budget.py` around lines 131 - 139, The validator
_validate_error_detail_consistency on CostRecordListResponse raises ValueError
on inconsistent error/error_detail but does not log; before each raise, call the
module/class logger (e.g., logger.warning or logger.error) with a clear
contextual message and include the offending field values (self.error,
self.error_detail) so the condition is observable, then raise the same
ValueError as before; update both branches (the "error_detail requires error to
be set" and "error must be accompanied by error_detail") to perform the log
first.
🤖 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/operations.md`:
- Around line 1006-1017: Remove the empty line immediately after the admonition
header `!!! info "Approval API Response Enrichment"` in
docs/design/operations.md so the paragraph following the header is not treated
as an indented code block; ensure the first line of the description starts
directly after the admonition header with no blank line to satisfy markdownlint
MD046.

---

Duplicate comments:
In `@src/synthorg/api/controllers/budget.py`:
- Around line 131-139: The validator _validate_error_detail_consistency on
CostRecordListResponse raises ValueError on inconsistent error/error_detail but
does not log; before each raise, call the module/class logger (e.g.,
logger.warning or logger.error) with a clear contextual message and include the
offending field values (self.error, self.error_detail) so the condition is
observable, then raise the same ValueError as before; update both branches (the
"error_detail requires error to be set" and "error must be accompanied by
error_detail") to perform the log first.

In `@web/src/api/endpoints/budget.ts`:
- Around line 36-47: The success branch trusts the returned body shape and can
throw a TypeError if fields are missing; guard the response before returning by
validating required properties on body (e.g., data,
pagination.total/offset/limit, daily_summary, period_summary) after
apiClient.get<CostRecordListResponseBody> and before the return, and if any
required field is absent or malformed throw an ApiRequestError with a clear
message (use body?.error_detail if present); update the logic around the body
variable and the return block to perform these checks so malformed success
payloads produce ApiRequestError instead of runtime TypeError.
🪄 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: 8b8947d9-9150-4d2b-8d96-deb1cc27b393

📥 Commits

Reviewing files that changed from the base of the PR and between 25d5247 and 593d8e8.

📒 Files selected for processing (14)
  • docs/design/communication.md
  • docs/design/operations.md
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/observability/events/approval_gate.py
  • tests/unit/api/controllers/test_approvals.py
  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_meetings.py
  • web/src/api/endpoints/approvals.ts
  • web/src/api/endpoints/budget.ts
  • web/src/api/endpoints/meetings.ts
  • 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: Test (Python 3.14)
  • GitHub Check: Build Backend
🧰 Additional context used
📓 Path-based instructions (7)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649.
Use PEP 758 except syntax: use except A, B: (no parentheses) -- ruff enforces this on Python 3.14.
Line length must be 88 characters (enforced by ruff).
Python code must pass ruff check and format (line length 88). Enforced by pre-commit and CI.

Files:

  • src/synthorg/observability/events/approval_gate.py
  • src/synthorg/observability/events/api.py
  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_approvals.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/approvals.py
  • tests/unit/api/controllers/test_meetings.py
  • src/synthorg/api/controllers/budget.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Type hints required on all public functions; mypy strict mode enforced.
Docstrings must use Google style and are required on public classes/functions (enforced by ruff D rules).
Create new objects instead of mutating existing ones. For non-Pydantic internal collections, use copy.deepcopy() at construction + MappingProxyType wrapping. For dict/list fields in frozen Pydantic models, rely on frozen=True and use copy.deepcopy() at system boundaries.
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.
Use Pydantic v2 conventions: BaseModel, model_validator, computed_field, ConfigDict. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr 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. Prefer structured concurrency over bare create_task. Existing code being migrated incrementally.
Functions must be less than 50 lines; files must be less than 800 lines.
Handle errors explicitly; never silently swallow errors.
Validate at system boundaries (user input, external APIs, config files).
Python type checking with mypy strict mode. Enforced by pre-push and CI.
API reference auto-generated via mkdocstrings + Griffe (AST-based from Python docstrings).

Files:

  • src/synthorg/observability/events/approval_gate.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST import logger via from synthorg.observability import get_logger then assign logger = get_logger(__name__).
Never use import logging, logging.getLogger(), or print() in application code (exception: observability/setup.py and observability/sinks.py may use stdlib logging and print for bootstrap code).
Logger variable must always be named logger (not _logger, not log).
Always 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). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
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 level for object creation, internal flow, and entry/exit of key functions.
Pure data models, enums, and re-exports do NOT need logging.
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code.
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, or large/medium/small. Vendor names allowed only in: Operations design page, .claude/ files, third-party import paths.

Files:

  • src/synthorg/observability/events/approval_gate.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Docs built with Zensical (Markdown). Config: mkdocs.yml. Design spec: docs/design/ (10 pages). Architecture: docs/architecture/. Roadmap: docs/roadmap/. Generate OpenAPI reference: scripts/export_openapi.py.

Files:

  • docs/design/communication.md
  • docs/design/operations.md
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow for test classification.
Prefer @pytest.mark.parametrize for testing similar cases.
Tests must use test-provider, test-small-001, etc. instead of vendor-specific names.
Use Hypothesis for property-based testing in Python (@given + @settings). Use fast-check in React (fc.assert + fc.property). Use native testing.F fuzz functions in Go (Fuzz*). Hypothesis profiles: ci (50 examples, default) and dev (1000 examples) 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() to make deterministic. For tasks that must block indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/api/controllers/test_budget.py
  • tests/unit/api/controllers/test_approvals.py
  • tests/unit/api/controllers/test_meetings.py
web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/**/*.{ts,tsx}: Use ESLint in React dashboard. Run with npm --prefix web run lint.
React TypeScript type checking via npm --prefix web run type-check.

Files:

  • web/src/api/endpoints/budget.ts
  • web/src/api/endpoints/meetings.ts
  • web/src/api/types.ts
  • web/src/api/endpoints/approvals.ts
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

React 19 dashboard with react-router config and auth/setup guards. Uses Zustand stores for state (auth, WebSocket, domain shells). Uses @tanstack/react-query for data fetching.

Files:

  • web/src/api/endpoints/budget.ts
  • web/src/api/endpoints/meetings.ts
  • web/src/api/types.ts
  • web/src/api/endpoints/approvals.ts
🧠 Learnings (45)
📚 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/api/**/*.py : Authentication uses JWT + API key. Approval gate integration for high-risk operations.

Applied to files:

  • src/synthorg/observability/events/approval_gate.py
  • docs/design/operations.md
  • src/synthorg/api/controllers/approvals.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: 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:

  • docs/design/operations.md
  • web/src/api/endpoints/budget.ts
  • src/synthorg/api/controllers/budget.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:

  • docs/design/operations.md
  • tests/unit/api/controllers/test_budget.py
  • src/synthorg/api/controllers/budget.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:

  • docs/design/operations.md
  • tests/unit/api/controllers/test_budget.py
  • web/src/api/endpoints/budget.ts
  • web/src/api/types.ts
  • src/synthorg/api/controllers/budget.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 : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/api.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/observability/events/api.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/observability/events/api.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:

  • src/synthorg/observability/events/api.py
  • src/synthorg/api/controllers/budget.py
📚 Learning: 2026-03-26T15:16:19.520Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:16:19.520Z
Learning: Applies to src/synthorg/**/*.py : Always 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). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/api.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/observability/events/api.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 event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/api.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 event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/api.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 : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/api.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/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/observability/events/api.py
📚 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 tests/**/*.py : 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

Applied to files:

  • tests/unit/api/controllers/test_approvals.py
📚 Learning: 2026-03-26T15:16:19.520Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:16:19.520Z
Learning: Applies to tests/**/*.py : 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 deterministic. For tasks that must block indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Applied to files:

  • tests/unit/api/controllers/test_approvals.py
📚 Learning: 2026-03-26T15:16:19.520Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:16:19.520Z
Learning: Applies to tests/**/*.py : Prefer pytest.mark.parametrize for testing similar cases.

Applied to files:

  • tests/unit/api/controllers/test_approvals.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 tests/**/*.py : Parametrize: Prefer pytest.mark.parametrize for testing similar cases.

Applied to files:

  • tests/unit/api/controllers/test_approvals.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 tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases.

Applied to files:

  • tests/unit/api/controllers/test_approvals.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 tests/**/*.py : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.

Applied to files:

  • tests/unit/api/controllers/test_approvals.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 tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).

Applied to files:

  • tests/unit/api/controllers/test_approvals.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:

  • src/synthorg/api/controllers/meetings.py
  • src/synthorg/api/controllers/approvals.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/approvals.py
  • tests/unit/api/controllers/test_meetings.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/approvals.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/api/controllers/approvals.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 **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values 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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-26T15:16:19.520Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:16:19.520Z
Learning: Applies to src/**/*.py : Use Pydantic v2 conventions: BaseModel, model_validator, computed_field, ConfigDict. Use computed_field for derived values instead of storing redundant fields. Use NotBlankStr for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • src/synthorg/api/controllers/approvals.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:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/approvals.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 **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-26T15:16:19.520Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:16:19.520Z
Learning: Applies to src/**/*.py : Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.

Applied to files:

  • src/synthorg/api/controllers/approvals.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 : 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:

  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.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 : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.py
📚 Learning: 2026-03-26T15:16:19.520Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:16:19.520Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.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/approvals.py
  • src/synthorg/api/controllers/budget.py
📚 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:

  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.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 : 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:

  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.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 : All error paths must log at WARNING or ERROR with context before raising

Applied to files:

  • src/synthorg/api/controllers/approvals.py
  • src/synthorg/api/controllers/budget.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/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • tests/unit/api/controllers/test_meetings.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:

  • tests/unit/api/controllers/test_meetings.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:

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

Applied to files:

  • src/synthorg/api/controllers/budget.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 : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • src/synthorg/api/controllers/budget.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 : Always log with structured kwargs: `logger.info(EVENT, key=value)` — never use old-style formatting `logger.info("msg %s", val)`

Applied to files:

  • src/synthorg/api/controllers/budget.py
🪛 markdownlint-cli2 (0.22.0)
docs/design/operations.md

[warning] 1008-1008: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

🔇 Additional comments (18)
src/synthorg/api/controllers/budget.py (1)

221-253: The Litestar Parameter(max_length=...) annotation does not crash the worker in version 2.21.1.

The comment at line 241 claims Parameter(max_length=...) crashes the worker, but Litestar 2.21.1 handles this gracefully by returning a proper RFC 9457–compliant 400 Bad Request response. The manual validation at lines 244+ serves a different purpose: it provides custom error messaging and structured logging with context (field name, actual length, max length), rather than preventing a crash. The annotation and manual validation can coexist without issue.

			> Likely an incorrect or invalid review comment.
tests/unit/api/controllers/test_approvals.py (2)

424-583: Well-structured urgency field tests with proper time mocking.

The new TestApprovalUrgencyFields class follows best practices:

  • Uses @pytest.mark.parametrize for TTL/urgency variants (lines 451-458, 546-551)
  • Freezes time via patch("synthorg.api.controllers.approvals.datetime") for boundary tests (lines 573-577)
  • Covers all endpoints (list, get, create, approve, reject) for urgency field presence

The boundary test properly mocks the clock the urgency calculation reads, making assertions deterministic.


585-609: LGTM — expired approval clamping test.

The test correctly verifies that expired approvals return seconds_remaining == 0.0 and urgency_level == "critical". Direct _items insertion (line 601) is appropriate here to bypass normal validation that would reject already-expired items.

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

32-32: LGTM — UrgencyLevel type correctly defined.

The union type matches the backend UrgencyLevel enum values: 'critical' | 'high' | 'normal' | 'no_expiry'.


279-282: LGTM — ApprovalResponse correctly extends ApprovalItem.

The extension adds the computed urgency fields while preserving all base fields, matching the backend DTO structure.


388-402: LGTM — Budget and meeting analytics DTOs.

DailySummary, PeriodSummary, and MeetingResponse types correctly match the backend response enrichments documented in PR objectives.

Also applies to: 765-769

web/src/api/endpoints/approvals.ts (1)

1-35: LGTM — Approval endpoint types updated to use enriched response.

All five endpoints (listApprovals, getApproval, createApproval, approveApproval, rejectApproval) now correctly return ApprovalResponse which includes the computed seconds_remaining and urgency_level fields.

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

69-129: Well-structured meeting minutes fixture with consistent participant data.

The _make_minutes helper now correctly:

  • Sets participant_ids to match contributor agent IDs when with_contributions=True (line 113-115)
  • Computes total_input_tokens/total_output_tokens from contributions (lines 110-111)
  • Sets deterministic duration (120 seconds via started_at = now - timedelta(seconds=120))

This addresses the previous concern about inconsistent participant fixture data.


287-304: Concrete analytics assertions in trigger response test.

The test now asserts specific values ({}, [], 120.0) rather than just key presence, ensuring regressions to empty defaults or wrong durations will be caught.


354-429: Comprehensive analytics test coverage.

The TestMeetingAnalytics class provides thorough coverage:

  • Token usage aggregation with exact expected values (lines 377-379)
  • Contribution rank ordering verification (line 388)
  • Duration computation for completed meetings (line 396)
  • Empty analytics for non-completed meetings (lines 404-406)
  • Edge case: completed meeting with no contributions (lines 419-429)
src/synthorg/api/controllers/meetings.py (3)

81-105: LGTM — MeetingResponse with analytics fields.

The response DTO correctly:

  • Extends MeetingRecord with computed analytics
  • Uses frozen=True for immutability
  • Applies ge=0.0 constraint on meeting_duration_seconds
  • Documents the behavior clearly in the docstring

108-140: Clean analytics computation in _to_meeting_response.

The helper correctly:

  • Aggregates tokens per participant (input + output)
  • Sorts by total tokens descending for contribution_rank
  • Clamps duration to non-negative (line 133)
  • Returns empty analytics when minutes is None

175-186: Manual validation retained with justification.

The comment at lines 175-177 explains why manual validation is kept despite Parameter(max_length=...): Litestar crashes the worker instead of returning RFC 9457 errors. The validation now logs API_VALIDATION_FAILED with field context before raising.

src/synthorg/api/controllers/approvals.py (5)

63-77: LGTM — UrgencyLevel enum with correct thresholds.

The enum and threshold constants correctly implement the PR specification:

  • critical < 1 hour (3600 seconds)
  • high < 4 hours (14400 seconds)
  • normal >= 4 hours
  • no_expiry when no TTL set

100-129: Clean urgency computation in _to_approval_response.

The helper correctly:

  • Returns None/NO_EXPIRY when expires_at is unset
  • Clamps seconds_remaining to >= 0.0 for expired items
  • Applies threshold logic in the correct order (critical → high → normal)

390-471: Well-factored helper functions reduce duplication.

_get_approval_or_404 and _save_decision_and_notify consolidate repeated patterns from approve/reject endpoints:

  • Centralized not-found logging and error raising
  • Single place for decision persistence, event publishing, logging, and resume signaling
  • Proper conflict handling with logging before raising

504-515: Manual validation with observability — justified.

The comment at lines 504-506 explains the Litestar crash issue. The validation now logs API_VALIDATION_FAILED with field context (field, actual_length, max_length) before raising, satisfying the observability requirement.


80-97: 🧹 Nitpick | 🔵 Trivial

Factory approach for urgency fields is acceptable.

While @computed_field would be more idiomatic for derived values, the current factory approach with frozen=True is pragmatic given:

  • Time-dependent computation requires a reference timestamp
  • Pydantic @computed_field doesn't support external parameters elegantly
  • Frozen config prevents mutation after construction

Consider documenting this design decision in the docstring if the team agrees on this pattern for time-dependent derived fields.

⛔ Skipped due to learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:16:19.520Z
Learning: Applies to src/**/*.py : 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.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to **/*.py : Config vs runtime state: use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state. Never mix static config fields with mutable runtime fields in one model.
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 frozen Pydantic models for config/identity; use 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.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T23:05:29.577Z
Learning: Applies to **/*.py : 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).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to **/*.py : 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.
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 : 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).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values 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.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
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.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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

🤖 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/operations.md`:
- Around line 1010-1013: The admonition block is being parsed as a code block
(MD046) because the list is separated/indented from the paragraph; fix by
tightening spacing so the bullet list begins immediately after the paragraph
line without an intervening blank line or extra indentation. Specifically,
ensure the lines for the list items referencing `seconds_remaining` and
`urgency_level` start at the same indentation as the paragraph/admonition marker
(no leading code-block indentation), so the bullets are recognized as a list
rather than an indented code block.
🪄 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: debb369f-2469-49df-a48e-2dd2e450566e

📥 Commits

Reviewing files that changed from the base of the PR and between 593d8e8 and 3462f49.

📒 Files selected for processing (1)
  • docs/design/operations.md
📜 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). (1)
  • GitHub Check: Test (Python 3.14)
🧰 Additional context used
📓 Path-based instructions (1)
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Docs built with Zensical (Markdown). Config: mkdocs.yml. Design spec: docs/design/ (10 pages). Architecture: docs/architecture/. Roadmap: docs/roadmap/. Generate OpenAPI reference: scripts/export_openapi.py.

Files:

  • docs/design/operations.md
🧠 Learnings (4)
📚 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: 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:

  • docs/design/operations.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/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:

  • docs/design/operations.md
📚 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:

  • docs/design/operations.md
📚 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/api/**/*.py : Authentication uses JWT + API key. Approval gate integration for high-risk operations.

Applied to files:

  • docs/design/operations.md
🪛 markdownlint-cli2 (0.22.0)
docs/design/operations.md

[warning] 1011-1011: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

🔇 Additional comments (1)
docs/design/operations.md (1)

212-218: Accurate contract update for budget summaries.

This section correctly states that daily_summary and period_summary are computed from all matching records (not page-limited), matching the controller flow and aggregation behavior.

Comment on lines +1010 to +1013

- **`seconds_remaining`** (`float | null`): seconds until `expires_at`, clamped
to 0.0 for expired items; `null` when no TTL is set.
- **`urgency_level`** (enum): `critical` (< 1 hr), `high` (< 4 hrs),
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

Fix MD046 warning in the admonition list block.

The list under this admonition is still being parsed as an indented code block by markdownlint (MD046). Tighten the spacing so the list starts directly after the paragraph line.

📝 Minimal formatting fix
 !!! info "Approval API Response Enrichment"
     The approval REST API enriches every `ApprovalItem` response with computed
     urgency fields so the dashboard can display time-sensitive indicators without
     client-side computation:
-
     - **`seconds_remaining`** (`float | null`): seconds until `expires_at`, clamped
       to 0.0 for expired items; `null` when no TTL is set.
📝 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
- **`seconds_remaining`** (`float | null`): seconds until `expires_at`, clamped
to 0.0 for expired items; `null` when no TTL is set.
- **`urgency_level`** (enum): `critical` (< 1 hr), `high` (< 4 hrs),
- **`seconds_remaining`** (`float | null`): seconds until `expires_at`, clamped
to 0.0 for expired items; `null` when no TTL is set.
- **`urgency_level`** (enum): `critical` (< 1 hr), `high` (< 4 hrs),
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 1011-1011: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

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

In `@docs/design/operations.md` around lines 1010 - 1013, The admonition block is
being parsed as a code block (MD046) because the list is separated/indented from
the paragraph; fix by tightening spacing so the bullet list begins immediately
after the paragraph line without an intervening blank line or extra indentation.
Specifically, ensure the lines for the list items referencing
`seconds_remaining` and `urgency_level` start at the same indentation as the
paragraph/admonition marker (no leading code-block indentation), so the bullets
are recognized as a list rather than an indented code block.

@Aureliolo Aureliolo merged commit 31472bf into main Mar 26, 2026
33 of 34 checks passed
@Aureliolo Aureliolo deleted the feat/approval-meeting-api-extensions branch March 26, 2026 15:49
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 26, 2026 15:49 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 30, 2026
🤖 I have created a release *beep* *boop*
---
#MAJOR CHANGES; We got a somewhat working webui :)

##
[0.5.0](v0.4.9...v0.5.0)
(2026-03-30)


### Features

* add analytics trends and budget forecast API endpoints
([#798](#798))
([16b61f5](16b61f5))
* add department policies to default templates
([#852](#852))
([7a41548](7a41548))
* add remaining activity event types (task_started, tool_used,
delegation, cost_incurred)
([#832](#832))
([4252fac](4252fac))
* agent performance, activity, and history API endpoints
([#811](#811))
([9b75c1d](9b75c1d))
* Agent Profiles and Detail pages (biography, career, performance)
([#874](#874))
([62d7880](62d7880))
* app shell, Storybook, and CI/CD pipeline
([#819](#819))
([d4dde90](d4dde90))
* Approvals page with risk grouping, urgency indicators, batch actions
([#889](#889))
([4e9673d](4e9673d))
* Budget Panel page (P&L dashboard, breakdown charts, forecast)
([#890](#890))
([b63b0f1](b63b0f1))
* build infrastructure layer (API client, auth, WebSocket)
([#815](#815))
([9f01d3e](9f01d3e))
* CLI global options infrastructure, UI modes, exit codes, env vars
([#891](#891))
([fef4fc5](fef4fc5))
* CodeMirror editor and theme preferences toggle
([#905](#905),
[#807](#807))
([#909](#909))
([41fbedc](41fbedc))
* Company page (department/agent management)
([#888](#888))
([cfb88b0](cfb88b0))
* comprehensive hint coverage across all CLI commands
([#900](#900))
([937974e](937974e))
* config system extensions, per-command flags for
init/start/stop/status/logs
([#895](#895))
([32f83fe](32f83fe))
* configurable currency system replacing hardcoded USD
([#854](#854))
([b372551](b372551))
* Dashboard page (metric cards, activity feed, budget burn)
([#861](#861))
([7d519d5](7d519d5))
* department health, provider status, and activity feed endpoints
([#818](#818))
([6d5f196](6d5f196))
* design tokens and core UI components
([#833](#833))
([ed887f2](ed887f2))
* extend approval, meeting, and budget API responses
([#834](#834))
([31472bf](31472bf))
* frontend polish -- real-time UX, accessibility, responsive,
performance ([#790](#790),
[#792](#792),
[#791](#791),
[#793](#793))
([#917](#917))
([f04a537](f04a537))
* implement human roles and access control levels
([#856](#856))
([d6d8a06](d6d8a06))
* implement semantic conflict detection in workspace merge
([#860](#860))
([d97283b](d97283b))
* interaction components and animation patterns
([#853](#853))
([82d4b01](82d4b01))
* Login page + first-run bootstrap + Company page
([#789](#789),
[#888](#888))
([#896](#896))
([8758e8d](8758e8d))
* Meetings page with timeline viz, token bars, contribution formatting
([#788](#788))
([#904](#904))
([b207f46](b207f46))
* Messages page with threading, channel badges, sender indicators
([#787](#787))
([#903](#903))
([28293ad](28293ad))
* Org Chart force-directed view and drag-drop reassignment
([#872](#872),
[#873](#873))
([#912](#912))
([a68a938](a68a938))
* Org Chart page (living nodes, status, CRUD, department health)
([#870](#870))
([0acbdae](0acbdae))
* per-command flags for remaining commands, auto-behavior wiring,
help/discoverability
([#897](#897))
([3f7afa2](3f7afa2))
* Providers page with backend rework -- health, CRUD, subscription auth
([#893](#893))
([9f8dd98](9f8dd98))
* scaffold React + Vite + TypeScript + Tailwind project
([#799](#799))
([bd151aa](bd151aa))
* Settings page with search, dependency indicators, grouped rendering
([#784](#784))
([#902](#902))
([a7b9870](a7b9870))
* Setup Wizard rebuild with template comparison, cost estimator, theme
customization ([#879](#879))
([ae8b50b](ae8b50b))
* setup wizard UX -- template filters, card metadata, provider form
reuse ([#910](#910))
([7f04676](7f04676))
* setup wizard UX overhaul -- mode choice, step reorder, provider fixes
([#907](#907))
([ee964c4](ee964c4))
* structured ModelRequirement in template agent configs
([#795](#795))
([7433548](7433548))
* Task Board page (rich Kanban, filtering, dependency viz)
([#871](#871))
([04a19b0](04a19b0))


### Bug Fixes

* align frontend types with backend and debounce WS refetches
([#916](#916))
([134c11b](134c11b))
* auto-cleanup targets newly pulled images instead of old ones
([#884](#884))
([50e6591](50e6591))
* correct wipe backup-skip flow and harden error handling
([#808](#808))
([c05860f](c05860f))
* improve provider setup in wizard, subscription auth, dashboard bugs
([#914](#914))
([87bf8e6](87bf8e6))
* improve update channel detection and add config get command
([#814](#814))
([6b137f0](6b137f0))
* resolve all ESLint warnings, add zero-warnings enforcement
([#899](#899))
([079b46a](079b46a))
* subscription auth uses api_key, base URL optional for cloud providers
([#915](#915))
([f0098dd](f0098dd))


### Refactoring

* semantic analyzer cleanup -- shared filtering, concurrency, extraction
([#908](#908))
([81372bf](81372bf))


### Documentation

* brand identity and UX design system from
[#765](#765) exploration
([#804](#804))
([389a9f4](389a9f4))
* page structure and information architecture for v0.5.0 dashboard
([#809](#809))
([f8d6d4a](f8d6d4a))
* write UX design guidelines with WCAG-verified color system
([#816](#816))
([4a4594e](4a4594e))


### Tests

* add unit tests for agent hooks and page components
([#875](#875))
([#901](#901))
([1d81546](1d81546))


### CI/CD

* bump actions/deploy-pages from 4.0.5 to 5.0.0 in the major group
([#831](#831))
([01c19de](01c19de))
* bump astral-sh/setup-uv from 7.6.0 to 8.0.0 in
/.github/actions/setup-python-uv in the all group
([#920](#920))
([5f6ba54](5f6ba54))
* bump codecov/codecov-action from 5.5.3 to 6.0.0 in the major group
([#868](#868))
([f22a181](f22a181))
* bump github/codeql-action from 4.34.1 to 4.35.0 in the all group
([#883](#883))
([87a4890](87a4890))
* bump sigstore/cosign-installer from 4.1.0 to 4.1.1 in the
minor-and-patch group
([#830](#830))
([7a69050](7a69050))
* bump the all group with 3 updates
([#923](#923))
([ff27c8e](ff27c8e))
* bump wrangler from 4.76.0 to 4.77.0 in /.github in the minor-and-patch
group ([#822](#822))
([07d43eb](07d43eb))
* bump wrangler from 4.77.0 to 4.78.0 in /.github in the all group
([#882](#882))
([f84118d](f84118d))


### Maintenance

* add design system enforcement hook and component inventory
([#846](#846))
([15abc43](15abc43))
* add dev-only auth bypass for frontend testing
([#885](#885))
([6cdcd8a](6cdcd8a))
* add pre-push rebase check hook
([#855](#855))
([b637a04](b637a04))
* backend hardening -- eviction/size-caps and model validation
([#911](#911))
([81253d9](81253d9))
* bump axios from 1.13.6 to 1.14.0 in /web in the all group across 1
directory ([#922](#922))
([b1b0232](b1b0232))
* bump brace-expansion from 5.0.4 to 5.0.5 in /web
([#862](#862))
([ba4a565](ba4a565))
* bump eslint-plugin-react-refresh from 0.4.26 to 0.5.2 in /web
([#801](#801))
([7574bb5](7574bb5))
* bump faker from 40.11.0 to 40.11.1 in the minor-and-patch group
([#803](#803))
([14d322e](14d322e))
* bump https://github.com/astral-sh/ruff-pre-commit from v0.15.7 to
0.15.8 ([#864](#864))
([f52901e](f52901e))
* bump nginxinc/nginx-unprivileged from `6582a34` to `f99cc61` in
/docker/web in the all group
([#919](#919))
([df85e4f](df85e4f))
* bump nginxinc/nginx-unprivileged from `ccbac1a` to `6582a34` in
/docker/web ([#800](#800))
([f4e9450](f4e9450))
* bump node from `44bcbf4` to `71be405` in /docker/sandbox
([#827](#827))
([91bec67](91bec67))
* bump node from `5209bca` to `cf38e1f` in /docker/web
([#863](#863))
([66d6043](66d6043))
* bump picomatch in /site
([#842](#842))
([5f20bcc](5f20bcc))
* bump recharts 2-&gt;3 and @types/node 22-&gt;25 in /web
([#802](#802))
([a908800](a908800))
* Bump requests from 2.32.5 to 2.33.0
([#843](#843))
([41daf69](41daf69))
* bump smol-toml from 1.6.0 to 1.6.1 in /site
([#826](#826))
([3e5dbe4](3e5dbe4))
* bump the all group with 3 updates
([#921](#921))
([7bace0b](7bace0b))
* bump the minor-and-patch group across 1 directory with 2 updates
([#829](#829))
([93e611f](93e611f))
* bump the minor-and-patch group across 1 directory with 3 updates
([#841](#841))
([7010c8e](7010c8e))
* bump the minor-and-patch group across 1 directory with 3 updates
([#869](#869))
([548cee5](548cee5))
* bump the minor-and-patch group in /site with 2 updates
([#865](#865))
([9558101](9558101))
* bump the minor-and-patch group with 2 updates
([#867](#867))
([4830706](4830706))
* consolidate Dependabot groups to 1 PR per ecosystem
([06d2556](06d2556))
* consolidate Dependabot groups to 1 PR per ecosystem
([#881](#881))
([06d2556](06d2556))
* improve worktree skill with full dep sync and status enhancements
([#906](#906))
([772c625](772c625))
* remove Vue remnants and document framework decision
([#851](#851))
([bf2adf6](bf2adf6))
* update web dependencies and fix brace-expansion CVE
([#880](#880))
([a7a0ed6](a7a0ed6))
* upgrade to Storybook 10 and TypeScript 6
([#845](#845))
([52d95f2](52d95f2))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: extend approval and meeting API responses

1 participant