* Make categories global
This solves us A LOT of cash flow and budgeting problems.
* Update schema.rb
* Update auto_categorizer.rb
* Update income_statement.rb
* FIX budget sub-categories
* FIX sub-categories and tests
* Add 2 step migration
* Add default family selection for invite-only onboarding mode
When onboarding is set to invite-only, admins can now choose a default
family that new users without an invitation are automatically placed into
as members, instead of creating a new family for each signup.
https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx
* Restrict invite codes and onboarding settings to super_admin only
The Invite Codes section on /settings/hosting was visible to any
authenticated user via the show action, leaking all family names/IDs
through the default-family dropdown. This tightens access:
- Hide the entire Invite Codes section in the view behind super_admin?
- Add before_action :ensure_super_admin to InviteCodesController for
all actions (index, create, destroy), replacing the inline admin? check
- Add ensure_super_admin_for_onboarding filter on hostings#update that
blocks non-super_admin users from changing onboarding_state or
invite_only_default_family_id
https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx
* Fix tests for super_admin-only invite codes and onboarding settings
- Hostings controller test: sign in as sure_support_staff (super_admin)
for the onboarding_state update test, since ensure_super_admin_for_onboarding
now requires super_admin role
- Invite codes tests: use super_admin fixture for the success case and
verify that a regular admin gets redirected instead of raising StandardError
https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx
* Fix system test to use super_admin for self-hosting settings
The invite codes section is now only visible to super_admin users,
so the system test needs to sign in as sure_support_staff to find
the onboarding_state select element.
https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx
* Skip invite code requirement when a default family is configured
When onboarding is invite-only but a default family is set, the
claim_invite_code before_action was blocking registration before
the create action could assign the user to the default family.
Now invite_code_required? returns false when
invite_only_default_family_id is present, allowing codeless
signups to land in the configured default family.
https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Enforce one pending invitation per email across all families
Users can only belong to one family, so allowing the same email to have
pending invitations from multiple families leads to ambiguous behavior.
Add a `no_other_pending_invitation` validation on create to prevent this.
Accepted and expired invitations from other families are not blocked.
Fixes#1172https://claude.ai/code/session_016fGqgha18jP48dhznm6k4m
* Normalize email before validation and use case-insensitive lookup
When ActiveRecord encryption is not configured, the email column stores
raw values preserving original casing. The prior validation used a direct
equality match which would miss case variants (e.g. Case@Test.com vs
case@test.com), leaving a gap in the cross-family uniqueness guarantee.
Fix by:
1. Adding a normalize_email callback that downcases/strips email before
validation, so all new records store lowercase consistently.
2. Using LOWER() in the SQL query for non-encrypted deployments to catch
any pre-existing mixed-case records.
https://claude.ai/code/session_016fGqgha18jP48dhznm6k4m
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Check for pending invitations before creating new Family during SSO account creation
When a user signs in via Google SSO and doesn't have an account yet, the
system now checks for pending invitations before creating a new Family.
If an invitation exists, the user joins the invited family instead.
- OidcAccountsController: check Invitation.pending in link/create_user
- API AuthController: check pending invitations in sso_create_account
- SessionsController: pass has_pending_invitation to mobile SSO callback
- Web view: show "Accept Invitation" button when invitation exists
- Flutter: show "Accept Invitation" tab/button when invitation pending
https://claude.ai/code/session_019Tr6edJa496V1ErGmsbqFU
* Fix external assistant tests: clear Settings cache to prevent test pollution
The tests relied solely on with_env_overrides to clear configuration, but
rails-settings-cached may retain stale Setting values across tests when
the cache isn't explicitly invalidated. Ensure both ENV vars AND Setting
values are cleared with Setting.clear_cache before assertions.
https://claude.ai/code/session_019Tr6edJa496V1ErGmsbqFU
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Add Google SSO onboarding flow for Flutter mobile app
Previously, mobile users attempting Google SSO without a linked OIDC
identity received an error telling them to link from the web app first.
This adds the same account linking/creation flow that exists on the PWA.
Backend changes:
- sessions_controller: Cache pending OIDC auth with a linking code and
redirect back to the app instead of returning an error
- api/v1/auth_controller: Add sso_link endpoint to link Google identity
to an existing account via email/password, and sso_create_account
endpoint to create a new SSO-only account (respects JIT config)
- routes: Add POST auth/sso_link and auth/sso_create_account
Flutter changes:
- auth_service: Detect account_not_linked callback status, add ssoLink
and ssoCreateAccount API methods
- auth_provider: Track SSO onboarding state, expose linking/creation
methods and cancelSsoOnboarding
- sso_onboarding_screen: New screen with tabs to link existing account
or create new account, pre-filled with Google profile data
- main.dart: Show SsoOnboardingScreen when ssoOnboardingPending is true
https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c
* Fix broken SSO tests: use MemoryStore cache and correct redirect param
- Sessions test: check `status` param instead of `error` since
handle_mobile_sso_onboarding sends linking info with status key
- API auth tests: swap null_store for MemoryStore so cache-based
linking code validation works in test environment
https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c
* Delay linking-code consumption until SSO link/create succeeds
Split validate_and_consume_linking_code into validate_linking_code
(read-only) and consume_linking_code! (delete). The code is now only
consumed after password verification (sso_link) or successful user
save (sso_create_account), so recoverable errors no longer burn the
one-time code and force a full Google SSO roundtrip.
https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c
* Make linking-code consumption atomic to prevent race conditions
Move consume_linking_code! (backed by Rails.cache.delete) to after
recoverable checks (bad password, policy rejection) but before
side-effecting operations (identity/user creation). Only the first
caller to delete the cache key gets true, so concurrent requests
with the same code cannot both succeed.
- sso_link: consume after password auth, before OidcIdentity creation
- sso_create_account: consume after allow_account_creation check,
before User creation
- Bad password still preserves the code for retry
- Add single-use regression tests for both endpoints
https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c
* Add missing sso_create_account test coverage for blank code and validation failure
- Test blank linking_code returns 400 (bad_request) with proper error
- Test duplicate email triggers user.save failure → 422 with validation errors
https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c
* Verify cache payload in mobile SSO onboarding test with MemoryStore
The test environment uses :null_store which silently discards cache
writes, so handle_mobile_sso_onboarding's Rails.cache.write was never
verified. Swap in a MemoryStore for this test and assert the full
cached payload (provider, uid, email, name, device_info,
allow_account_creation) at the linking_code key from the redirect URL.
https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c
* Add rswag/OpenAPI specs for sso_link and sso_create_account endpoints
POST /api/v1/auth/sso_link: documents linking_code + email/password
params, 200 (tokens), 400 (missing code), 401 (invalid creds/expired).
POST /api/v1/auth/sso_create_account: documents linking_code +
optional first_name/last_name params, 200 (tokens), 400 (missing code),
401 (expired code), 403 (creation disabled), 422 (validation errors).
Note: RAILS_ENV=test bundle exec rake rswag:specs:swaggerize should be
run to regenerate docs/api/openapi.yaml once the runtime environment
matches the Gemfile Ruby version.
https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c
* Preserve OIDC issuer through mobile SSO onboarding flow
handle_mobile_sso_onboarding now caches the issuer from
auth.extra.raw_info.iss so it survives the linking-code round trip.
build_omniauth_hash populates extra.raw_info.iss from the cached
issuer so OidcIdentity.create_from_omniauth stores it correctly.
Previously the issuer was always nil for mobile SSO-created identities
because build_omniauth_hash passed an empty raw_info OpenStruct.
https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c
* Block MFA users from bypassing second factor via sso_link
sso_link authenticated with email/password but never checked
user.otp_required?, allowing MFA users to obtain tokens without
a second factor. The mobile SSO callback already rejects MFA users
with "mfa_not_supported"; apply the same guard in sso_link before
consuming the linking code or creating an identity.
Returns 401 with mfa_required: true, consistent with the login
action's MFA response shape.
https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c
* Fix NoMethodError in SSO link MFA test
Replace non-existent User.generate_otp_secret class method with
ROTP::Base32.random(32), matching the pattern used in User#setup_mfa!.
https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c
* Assert linking code survives rejected SSO create account
Add cache persistence assertion to "should reject SSO create account
when not allowed" test, verifying the linking code is not consumed on
the 403 path. This mirrors the pattern used in the invalid-password
sso_link test.
The other rejection tests (expired/missing linking code) don't have a
valid cached code to check, so no assertion is needed there.
https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Display user admins grouped
* Start family/groups collapsed
* Sort by number of transactions
* Display subscription status
* Fix tests
* Use Stimulus
Both Uncategorized and Other Investments are synthetic categories with
id=nil. When expense_totals_by_category indexes by category.id, Other
Investments overwrites Uncategorized at the nil key, causing uncategorized
actual spending to always return 0.
Use category.name as fallback key (id || name) to differentiate the two
synthetic categories in all hash builders and lookup sites.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add new Date field when creating a new Account
* Fix german translation
* Update app/controllers/concerns/accountable_resource.rb
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Michel Roegl-Brunner <73236783+michelroegl-brunner@users.noreply.github.com>
* Add missing opening_balance:date to update_params
* Change label text
---------
Signed-off-by: Michel Roegl-Brunner <73236783+michelroegl-brunner@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* feat: add new UI component to display dropdown select with filter
* feat: use new dropdown componet for category selection in transactions
* feat: improve dropdown controller
* feat: Add checkbox indicator to highlight selected element in list
* feat: add possibility to define dropdown without search
* feat: initial implementation of variants
* feat: Add default color for dropdown menu
* feat: add "icon" variant for dropdown
* refactor: component + controller refactoring
* refactor: view + component
* fix: adjust min width in selection for mobile
* feat: refactor collection_select method to use new filter dropdown component
* fix: compute fixed position for dropdown
* feat: controller improvements
* lint issues
* feat: add dot color if no icon is available
* refactor: controller refactor + update naming for variant from icon to logo
* fix: set width to 100% for select dropdown
* feat: add variant to collection_select in new transaction form
* fix: typo in placeholder value
* fix: add back include_blank property
* refactor: rename component from FilterDropdown to Select
* fix: translate placeholder and keep value_method and text_method
* fix: remove duplicate variable assignment
* fix: translate placeholder
* fix: verify color format
* fix: use right autocomplete value
* fix: selection issue + controller adjustments
* fix: move calls to startAutoUpdate and stopAutoUpdate
* Update app/javascript/controllers/select_controller.js
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Alessio Cappa <104093777+alessiocappa@users.noreply.github.com>
* fix: add aria-labels
* fix: pass html_options to DS::Select
* fix: unnecessary closing tag
* fix: use offsetvalue for position checks
* fix: use right classes for dropdown transitions
* include options[:prompt] in placeholder init
* fix: remove unused locale key
* fix: Emit a native change event after updating the input value.
* fix: Guard against negative maxHeight in constrained layouts.
* fix: Update test
* fix: lint issues
* Update test/system/transfers_test.rb
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Alessio Cappa <104093777+alessiocappa@users.noreply.github.com>
* Update test/system/transfers_test.rb
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Alessio Cappa <104093777+alessiocappa@users.noreply.github.com>
* refactor: move CSS class for button select form in maybe-design-system.css
---------
Signed-off-by: Alessio Cappa <104093777+alessiocappa@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* Feat: Implement manual sync prices functionality and enhance holdings display
* Feat: Enhance sync prices functionality with error handling and update UI components
* Feat: Update sync prices error handling and enhance Spanish locale messages
* Fix: Address CodeRabbit review feedback
- Set fallback @provider_error when prices_updated == 0 so turbo stream
never fails silently without a visible error message
- Move attr_reader :provider_error to class header in Price::Importer
for conventional placement alongside other attribute declarations
- Precompute @last_price_updated in controller (show + sync_prices)
instead of running a DB query directly inside ERB templates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix: Replace bare rescue with explicit exception handling in turbo stream view
Bare `rescue` silently swallows all exceptions, making debugging impossible.
Match the pattern already used in show.html.erb: rescue ActiveRecord::RecordInvalid
explicitly, then catch StandardError with logging (message + backtrace) before
falling back to the unknown label.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix: Update test assertion to expect actual provider error message
The stub returns "Yahoo Finance rate limit exceeded" as the provider error.
After the @provider_error fallback fix, the controller now correctly surfaces
the real provider error when present (using .presence || fallback), so the
flash[:alert] is the actual error string, not the generic fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix: Assert scoped security_ids in sync_prices materializer test
Replace loose stub with constructor expectation to verify that
Balance::Materializer is instantiated with the single-security scope.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix: Assert holding remap in remap_security test
Add assertion that @holding.security_id is updated to the target
security after remap, covering the core command outcome.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix: CI test failure - Update disconnect external assistant test to use env overrides
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The `subcategories` method queries `WHERE parent_id = category.id`, but
for the synthetic uncategorized budget category, `category.id` is nil.
This caused `WHERE parent_id IS NULL` to match ALL top-level categories,
making them appear as subcategories of uncategorized. This inflated
actual_spending and produced a large negative available_to_spend.
Add a nil guard on category.id to return an empty relation for synthetic
categories.
Fixes#819
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add budget rollover: copy from previous month
When navigating to an uninitialized budget month, show a prompt
offering to copy amounts from the most recent initialized budget.
Copies budgeted_spending, expected_income, and all matching category
allocations. Also fixes over-allocation warning showing on uninitialized
budgets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Redirect copy_previous to categories wizard for review
Matches the normal budget setup flow (edit → categories → show)
so users can review/tweak copied allocations before confirming.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Address code review: eager-load categories, guard against overwrite
- Add .includes(:budget_categories) to most_recent_initialized_budget
to avoid N+1 when copy_from! iterates source categories
- Guard copy_previous action against overwriting already-initialized
budgets (prevents crafted POST from clobbering existing data)
- Add i18n key for already_initialized flash message
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add invariant guards to copy_from! for defensive safety
Validate that source budget belongs to the same family and precedes
the target budget before copying. Protects against misuse from
other callers beyond the controller.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix button overflow on small screens in copy previous prompt
Stack buttons vertically on mobile, side-by-side on sm+ breakpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(helm): add Pipelock ConfigMap, scanning config, and consolidate compose
- Add ConfigMap template rendering DLP, response scanning, MCP input/tool
scanning, and forward proxy settings from values
- Mount ConfigMap as /etc/pipelock/pipelock.yaml volume in deployment
- Add checksum/config annotation for automatic pod restart on config change
- Gate HTTPS_PROXY/HTTP_PROXY env injection on forwardProxy.enabled (skip
in MCP-only mode)
- Use hasKey for all boolean values to prevent Helm default swallowing false
- Single source of truth for ports (forwardProxy.port/mcpProxy.port)
- Pipelock-specific imagePullSecrets with fallback to app secrets
- Merge standalone compose.example.pipelock.yml into compose.example.ai.yml
- Add pipelock.example.yaml for Docker Compose users
- Add exclude-paths to CI workflow for locale file false positives
* Add external assistant support (OpenAI-compatible SSE proxy)
Allow self-hosted instances to delegate chat to an external AI agent
via an OpenAI-compatible streaming endpoint. Configurable per-family
through Settings UI or ASSISTANT_TYPE env override.
- Assistant::External::Client: SSE streaming HTTP client (no new gems)
- Settings UI with type selector, env lock indicator, config status
- Helm chart and Docker Compose env var support
- 45 tests covering client, config, routing, controller, integration
* Add session key routing, email allowlist, and config plumbing
Route to the actual OpenClaw session via x-openclaw-session-key header
instead of creating isolated sessions. Gate external assistant access
behind an email allowlist (EXTERNAL_ASSISTANT_ALLOWED_EMAILS env var).
Plumb session_key and allowedEmails through Helm chart, compose, and
env template.
* Add HTTPS_PROXY support to External::Client for Pipelock integration
Net::HTTP does not auto-read HTTPS_PROXY/HTTP_PROXY env vars (unlike
Faraday). Explicitly resolve proxy from environment in build_http so
outbound traffic to the external assistant routes through Pipelock's
forward proxy when enabled. Respects NO_PROXY for internal hosts.
* Add UI fields for external assistant config (Setting-backed with env fallback)
Follow the same pattern as OpenAI settings: database-backed Setting
fields with env var defaults. Self-hosters can now configure the
external assistant URL, token, and agent ID from the browser
(Settings > Self-Hosting > AI Assistant) instead of requiring env vars.
Fields disable when the corresponding env var is set.
* Improve external assistant UI labels and add help text
Change placeholder to generic OpenAI-compatible URL pattern. Add help
text under each field explaining where the values come from: URL from
agent provider, token for authentication, agent ID for multi-agent
routing.
* Add external assistant docs and fix URL help text
Add External AI Assistant section to docs/hosting/ai.md covering setup
(UI and env vars), how it works, Pipelock security scanning, access
control, and Docker Compose example. Drop "chat completions" jargon
from URL help text.
* Harden external assistant: retry logic, disconnect UI, error handling, and test coverage
- Add retry with backoff for transient network errors (no retry after streaming starts)
- Add disconnect button with confirmation modal in self-hosting settings
- Narrow rescue scope with fallback logging for unexpected errors
- Safe cleanup of partial responses on stream interruption
- Gate ai_available? on family assistant_type instead of OR-ing all providers
- Truncate conversation history to last 20 messages
- Proxy-aware HTTP client with NO_PROXY support
- Sanitize protocol to use generic headers (X-Agent-Id, X-Session-Key)
- Full test coverage for streaming, retries, proxy routing, config, and disconnect
* Exclude external assistant client from Pipelock scan-diff
False positive: `@token` instance variable flagged as "Credential in URL".
Temporary workaround until Pipelock supports inline suppression.
* Address review feedback: NO_PROXY boundary fix, SSE done flag, design tokens
- Fix NO_PROXY matching to require domain boundary (exact match or .suffix),
case-insensitive. Prevents badexample.com matching example.com.
- Add done flag to SSE streaming so read_body stops after [DONE]
- Move MAX_CONVERSATION_MESSAGES to class level
- Use bg-success/bg-destructive design tokens for status indicators
- Add rationale comment for pipelock scan exclusion
- Update docs last-updated date
* Address second round of review feedback
- Allowlist email comparison is now case-insensitive and nil-safe
- Cap SSE buffer at 1 MB to prevent memory blowup from malformed streams
- Don't expose upstream HTTP response body in user-facing errors (log it instead)
- Fix frozen string warning on buffer initialization
- Fix "builtin" typo in docs (should be "built-in")
* Protect completed responses from cleanup, sanitize error messages
- Don't destroy a fully streamed assistant message if post-stream
metadata update fails (only cleanup partial responses)
- Log raw connection/HTTP errors internally, show generic messages
to users to avoid leaking network/proxy details
- Update test assertions for new error message wording
* Fix SSE content guard and NO_PROXY test correctness
Use nil check instead of present? for SSE delta content to preserve
whitespace-only chunks (newlines, spaces) that can occur in code output.
Fix NO_PROXY test to use HTTP_PROXY matching the http:// client URL so
the proxy resolution and NO_PROXY bypass logic are actually exercised.
* Forward proxy credentials to Net::HTTP
Pass proxy_uri.user and proxy_uri.password to Net::HTTP.new so
authenticated proxies (http://user:pass@host:port) work correctly.
Without this, credentials parsed from the proxy URL were silently
dropped. Nil values are safe as positional args when no creds exist.
* Update pipelock integration to v0.3.1 with full scanning config
Bump Helm image tag from 0.2.7 to 0.3.1. Add missing security
sections to both the Helm ConfigMap and compose example config:
mcp_tool_policy, mcp_session_binding, and tool_chain_detection.
These protect the /mcp endpoint against tool injection, session
hijacking, and multi-step exfiltration chains.
Add version and mode fields to config files. Enable include_defaults
for DLP and response scanning to merge user patterns with the 35
built-in patterns. Remove redundant --mode CLI flag from the Helm
deployment template since mode is now in the config file.
* Fix: use cookie/crumb auth in healthy? chart endpoint check
The health check was calling /v8/finance/chart/AAPL via the plain
unauthenticated client. Yahoo Finance requires cookie + crumb
authentication on the chart endpoint, so the health check would
fail even when credentials are valid. Updated healthy? to use
fetch_cookie_and_crumb + authenticated_client, consistent with
fetch_security_prices and fetch_chart_data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix: add cookie/crumb auth to all /v8/finance/chart/ calls
fetch_security_prices and fetch_chart_data (used for exchange rates)
were calling the chart endpoint without cookie/crumb authentication,
inconsistent with healthy? and fetch_security_info. Added auth to both,
including the same retry-on-Unauthorized pattern already used in
fetch_security_info.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update user-agent strings in yahoo_finance.rb
Updated user-agent strings to reflect current browser versions
Signed-off-by: Serge L <serge@souritech.ca>
* Fix: Add stale-crumb retry to healthy? and fetch_chart_data
Yahoo Finance returns 200 OK with {"chart":{"error":{"code":"Unauthorized"}}}
when a cached crumb expires server-side. Both healthy? and fetch_chart_data
now mirror the retry pattern already in fetch_security_prices: detect the
Unauthorized body, clear the crumb cache, fetch fresh credentials, and
retry the request once. Adds a test for the healthy? retry path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Refactor: Extract fetch_authenticated_chart helper to DRY crumb retry logic
The cookie/crumb fetch + stale-crumb retry pattern was duplicated across
healthy?, fetch_security_prices, and fetch_chart_data. Extract it into a
single private fetch_authenticated_chart(symbol, params) helper that
centralizes the retry logic; all three call sites now delegate to it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix: Catch JSON::ParserError in fetch_chart_data rescue clause
After moving JSON.parse inside fetch_authenticated_chart, a malformed
Yahoo response would throw JSON::ParserError through fetch_chart_data's
rescue Faraday::Error, breaking the inverse currency pair fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix: Raise AuthenticationError if retry still returns Unauthorized
After refreshing the crumb and retrying, if Yahoo still returns an
Unauthorized error body the helper now raises AuthenticationError instead
of silently returning the error payload. This prevents callers from
misinterpreting a persistent auth failure as missing chart data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix: Raise AuthenticationError after failed retry in fetch_security_info
Mirrors the same post-retry Unauthorized check added to fetch_authenticated_chart.
Without this, a persistent auth failure on the quoteSummary endpoint would
surface as a generic "No security info found" error instead of an AuthenticationError.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Signed-off-by: Serge L <serge@souritech.ca>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The polling controller was requesting turbo_stream format but only an
HTML template exists. Fix the Accept header to request text/html and
handle both formats in the controller.
Replace direct trace.update calls with client trace upserts so OpenAI provider is compatible with langfuse-ruby 0.1.6 behavior. Add richer warning logs that include full exception details for trace creation, trace upserts, and generation logging failures. Add tests for client-based trace upserts and detailed error logging.
* Add MCP server endpoint for external AI assistants
Expose Sure's Assistant::Function tools via JSON-RPC 2.0 at POST /mcp,
enabling external AI clients (Claude, GPT, etc.) to query financial data
through the Model Context Protocol.
- Bearer token auth via MCP_API_TOKEN / MCP_USER_EMAIL env vars
- JSON-RPC 2.0 with proper id threading, notification handling (204)
- Transient session (sessions.build) to prevent impersonation leaks
- Centralize function_classes in Assistant module
- Docker Compose example with Pipelock forward proxy
- 18 integration tests with scoped env (ClimateControl)
* Update compose for full Pipelock MCP reverse proxy integration
Use Pipelock's --mcp-listen/--mcp-upstream flags (PR #127) to run
bidirectional MCP scanning in the same container as the forward proxy.
External AI clients connect to port 8889, Pipelock scans requests
(DLP, injection, tool policy) and responses (injection, tool poisoning)
before forwarding to Sure's /mcp endpoint.
This supersedes the standalone compose in PR #1050.
* Fix compose --preset→--mode, add port 3000 trust comment, notification test
Review fixes:
- pipelock run uses --mode not --preset (would prevent stack startup)
- Document port 3000 exposes /mcp directly (auth still required)
- Add version requirement note for Pipelock MCP listener support
- Add test: tools/call sent as notification does not execute
* Chat improvements
* Delete/reset account via API for Flutter app
* Fix tests.
* Add "contact us" to settings
* Update mobile/lib/screens/chat_conversation_screen.dart
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Juan José Mata <jjmata@jjmata.com>
* Improve LLM special token detection
* Deactivated user shouldn't have API working
* Fix tests
* API-Key usage
* Flutter app launch failure on no network
* Handle deletion/reset delays
* Local cached data may become stale
* Use X-Api-Key correctly!
---------
Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* fix: crypto subtype not persisted by permitting :subtype in CryptosController
* Backfill crypto subtype for existig accounts so Trades API works
* fix: backfill only unlinked cryptos; use raw SQL in migration; deterministic redirect in test
* Update schema.rb for BackfillcryptoSubtypeForTrades migration
---------
Signed-off-by: dataCenter430 <161712630+dataCenter430@users.noreply.github.com>
Balance sheet totals and accountable type summaries used a SQL JOIN on
exchange_rates matching only today's date, which returned NULL (defaulting
to 1:1) when no rate existed for that exact date. This caused foreign
currency accounts to show incorrect totals.
Changes:
- Refactor BalanceSheet::AccountTotals to batch-fetch exchange rates via
ExchangeRate.rates_for, with provider fallback, instead of a SQL join
- Refactor Accountable.balance_money to use the same batch approach
- Add ExchangeRate.rates_for helper for deduplicated rate lookups
- Fix net worth chart query to fall back to the nearest future rate when
no historical rate exists for a given date
- Add composite index on accounts (family_id, status, accountable_type)
- Reuse nearest cached exchange rate within a 5-day lookback window
before calling the provider, preventing redundant API calls on
weekends and holidays when providers return prior-day rates
https://claude.ai/code/session_01GyssBJxQqdWnuYofQRjUu8
Co-authored-by: Claude <noreply@anthropic.com>
* fix: Include investment_contribution in transfer? check and protect transfer entries from sync
Transfer transactions with kind "investment_contribution" were not recognized
as transfers by the UI, causing missing +/- indicators, "Transfer" labels,
and showing regular transaction forms instead of transfer details.
Also adds user_modified: true to entries created via TransferMatchesController
and SetAsTransferOrPayment rule action to protect them from provider sync
overwrites, matching the existing behavior in Transfer::Creator.
https://claude.ai/code/session_019BZ5Z1aqKSK3cRdR81P5Jg
* fix: Centralize transfer/budget kind constants for consistent investment_contribution handling
Define TRANSFER_KINDS and BUDGET_EXCLUDED_KINDS on Transaction to eliminate
hard-coded kind lists scattered across filters, rules, and analytics code.
investment_contribution is now consistently treated as a transfer in search
filters, rule conditions, and UI display (via TRANSFER_KINDS), while budget
analytics correctly continue treating it as an expense (via BUDGET_EXCLUDED_KINDS).
https://claude.ai/code/session_019BZ5Z1aqKSK3cRdR81P5Jg
* fix: Update tests for consistent investment_contribution as transfer kind
- search_test: loan_payment is now in TRANSFER_KINDS, so uncategorized
filter correctly excludes it (same as funds_movement/cc_payment)
- condition_test: investment_contribution is now a transfer kind, so it
matches the transfer filter rather than expense filter
https://claude.ai/code/session_019BZ5Z1aqKSK3cRdR81P5Jg
* fix: Eliminate SQL injection warnings in Transaction::Search
Replace string-interpolated SQL with parameterized queries:
- totals: use sanitize_sql_array with ? placeholders
- apply_category_filter: pass TRANSFER_KINDS as bind parameter
- apply_type_filter: use where(kind:)/where.not(kind:) and
parameterized IN (?) for compound OR conditions
- Remove unused transfer_kinds_sql helper
https://claude.ai/code/session_019BZ5Z1aqKSK3cRdR81P5Jg
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Replace text-tertiary with text-subdued in views
Replace usages of the text-tertiary utility with text-subdued across several view partials to standardize subdued text styling because text-tertiary does not exist in the design system (reports, doorkeeper auth, simplefin items). Also adjust the net worth empty-liabilities markup to use a grid layout for consistent spacing, and update the related controller test selector to match the new CSS class.
* Standardize empty net worth message markup
Replace inconsistent markup and classes for empty asset/liability sections in the net worth partial. Swap text-secondary/p-2/text-center for text-subdued with unified padding (py-3 px-4 lg:px-6), and simplify the liabilities block from a grid/div to a single paragraph for consistent styling and spacing.
* fix: locale-dependent category duplication bug
* fix: use family locale for investment contributions category to prevent duplicates and handle legacy data
* Remove v* tag trigger from flutter-build to fix double-runs
publish.yml already calls flutter-build via workflow_call on v* tags,
so the direct push trigger was causing duplicate workflow runs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Refactor mobile release asset flow
* fix: category uniqueness and workflow issues
* fix: fix test issue
* fix: solve test issue
* fix: resolve legacy problem
* fix: solve lint test issue
* fix: revert unrelated changes
---------
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Show disabled import options before accounts exist
Keep account-dependent import choices visible on /imports/new and render them as disabled with guidance when no accounts are available.
* Refactor disabled import options: extract partial, fix accessibility (#986)
- Extract _import_option partial to eliminate duplicated enabled/disabled
markup across TransactionImport, TradeImport, and MintImport (also
used by AccountImport, CategoryImport, RuleImport for consistency)
- Replace misleading chevron-right with lock icon in disabled state
- Add aria-disabled="true" for screen reader accessibility
- Remove redundant default: parameter from t() call
- Fix locale key ordering (requires_account after import_* keys)
- Fix extra blank line in test file
- Add assertion for aria-disabled attribute in test
https://claude.ai/code/session_016j9tDYEBfWX9Dzd99rAYjX
Co-authored-by: Claude <noreply@anthropic.com>
* Tailwind fixes
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Wire ui layout and AI flags into mobile auth
Include ui_layout and ai_enabled in mobile login/signup/SSO payloads,
add an authenticated endpoint to enable AI from Flutter, and gate
mobile navigation based on intro layout and AI consent flow.
* Linter
* Ensure write scope on enable_ai
* Make sure AI is available before enabling it
* Test improvements
* PR comment
* Fix review issues: test assertion bug, missing coverage, and Dart defaults (#985)
- Fix login test to use ai_enabled? (method) instead of ai_enabled (column)
to match what mobile_user_payload actually serializes
- Add test for enable_ai when ai_available? returns false (403 path)
- Default aiEnabled to false when user is null in AuthProvider to avoid
showing AI as available before authentication completes
- Remove extra blank lines in auth_provider.dart and auth_service.dart
https://claude.ai/code/session_01LEYYmtsDBoqizyihFtkye4
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix: Handle empty compound conditions on rules index
* fix: avoid contradictory rule condition summary on /rules
* refactor: move rules condition display logic from view to model
* fix: localize rule title fallback and preload conditions in rules index
* Add family moniker selection and dynamic UI labels
Introduce a Family moniker persisted in the database with allowed values Family/Group, add required onboarding selection for it, and thread moniker-aware copy through key user-facing views and locales. Also add helper methods and tests for onboarding form presence and family moniker behavior.
* Small copy edits/change moniker question order
* Conditional Group/Family onboarding flow fixes
* Fix label
* Grouping of fields
* Profile Info page Group/Family changes
* Only admins can change Group/Family moniker
* Repetitive defaults
* Moniker in Account model
* Moniker in User model
* Auth fix
* Sure product is also a moniker
---------
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
* Add SearchFamilyImportedFiles assistant function with vector store support
Implement per-Family document search using OpenAI vector stores, allowing
the AI assistant to search through uploaded financial documents (tax returns,
statements, contracts, etc.). The architecture is modular with a provider-
agnostic VectorStoreConcept interface so other RAG backends can be added.
Key components:
- Assistant::Function::SearchFamilyImportedFiles - tool callable from any LLM
- Provider::VectorStoreConcept - abstract vector store interface
- Provider::Openai vector store methods (create, upload, search, delete)
- Family::VectorSearchable concern with document management
- FamilyDocument model for tracking uploaded files
- Migration adding vector_store_id to families and family_documents table
https://claude.ai/code/session_01TSkKc7a9Yu2ugm1RvSf4dh
* Extract VectorStore adapter layer for swappable backends
Replace the Provider::VectorStoreConcept mixin with a standalone adapter
architecture under VectorStore::. This cleanly separates vector store
concerns from the LLM provider and makes it trivial to swap backends.
Components:
- VectorStore::Base — abstract interface (create/delete/upload/remove/search)
- VectorStore::Openai — uses ruby-openai gem's native vector_stores.search
- VectorStore::Pgvector — skeleton for local pgvector + embedding model
- VectorStore::Qdrant — skeleton for Qdrant vector DB
- VectorStore::Registry — resolves adapter from VECTOR_STORE_PROVIDER env
- VectorStore::Response — success/failure wrapper (like Provider::Response)
Consumers updated to go through VectorStore.adapter:
- Family::VectorSearchable
- Assistant::Function::SearchFamilyImportedFiles
- FamilyDocument
Removed: Provider::VectorStoreConcept, vector store methods from Provider::Openai
https://claude.ai/code/session_01TSkKc7a9Yu2ugm1RvSf4dh
* Add Vector Store configuration docs to ai.md
Documents how to configure the document search feature, covering all
three supported backends (OpenAI, pgvector, Qdrant), environment
variables, Docker Compose examples, supported file types, and privacy
considerations.
https://claude.ai/code/session_01TSkKc7a9Yu2ugm1RvSf4dh
* No need to specify `imported` in code
* Missed a couple more places
* Tiny reordering for the human OCD
* Update app/models/assistant/function/search_family_files.rb
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Juan José Mata <jjmata@jjmata.com>
* PR comments
* More PR comments
---------
Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* Fix property subtype not persisting on edit
* Add regression test for property subtype persistence
This change introduces model specs and factories to cover
property subtype persistence on update.
FactoryBot setup and test dependencies were adjusted to
support the new specs.
* Add regression test for property subtype persistence
* remove unused FactoryBot factories and test
* remove FactoryBot in Gemfile.lock
* Fix no-op regression test for property subtype update
* Delete no-op property_test
* add pimary_residence in properties fixtures
* add capybara system test for property subtype persistence
* fix spelling and indent
* rename test to "can persist property subtype"
Signed-off-by: HugoleDino <135261771+HugoleDino@users.noreply.github.com>
---------
Signed-off-by: HugoleDino <135261771+HugoleDino@users.noreply.github.com>
* Add LLM prompt for API endpoint consistency (fixes#944)
- Add .cursor/rules/api-endpoint-consistency.mdc: checklist that applies
when editing app/controllers/api/v1, spec/requests/api/v1, or
test/controllers/api/v1. Enforces (1) Minitest-only behavioral coverage
for new endpoints, (2) rswag docs-only (no expect/assert), (3) same
API key auth pattern in all rswag specs.
- Reference the rule in AGENTS.md under API Development Guidelines.
* Add tests for API endpoint consistency implementation
- Minitest: test/api_endpoint_consistency_rule_test.rb checks rule file
exists, globs, and all three sections (Minitest, rswag docs-only,
API key auth) plus AGENTS.md reference.
- Standalone: test/support/verify_api_endpoint_consistency.rb runs
same checks without loading Rails (use when app fails to boot).
- Rule: add mdc: links for Cursor, note valuations_spec OAuth outlier.
* Address review: add --compliance check, CLAUDE.md section
- Verification script: --compliance scans current APIs and reports
rswag OAuth vs API key, missing Minitest for controllers, expect/assert.
- CLAUDE.md: add Post-commit API consistency subsection under
API Development Guidelines (links to rule, documents script + --compliance).
---------
Co-authored-by: mkdev11 <jaysmth689+github@users.noreply.github.com>
* First cut of a simplified "intro" UI layout
* Linter
* Add guest role and intro-only access
* Fix guest role UI defaults (#940)
Use enum predicate to avoid missing role helper.
* Remove legacy user role mapping (#941)
Drop the unused user role references in role normalization
and SSO role mapping forms to avoid implying a role that
never existed.
Refs: #0
* Remove role normalization (#942)
Remove role normalization
Roles are now stored directly without legacy mappings.
* Revert role mapping logic
* Remove `normalize_role_settings`
* Remove unnecessary migration
* Make `member` the default
* Broken `.erb`
---------
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
* Add Indexa Capital provider scaffold
Generate Indexa Capital provider scaffolding and align credential fields with the API authentication requirements.
* Fix PR 926 lint and schema CI failures
* Implement Indexa Capital provider with real API integration
- Rewrite all broken view templates (were meta-ERB from code generator)
- Create missing select_accounts.html.erb template
- Implement real API calls: list_accounts via /users/me, get_holdings
via /accounts/{number}/fiscal-results, get_account_balance via
/accounts/{number}/performance
- Add API token auth support (stored token > env token > credentials)
- Add api_token column with encryption support
- Redesign settings panel: API token prominent, credentials collapsible
- Fix account balances display using performance endpoint portfolios
- Fix accounts index empty-state guard missing indexa_capital_items
- Simplify activities fetch job (no activities API endpoint exists)
- Fix i18n interpolation (%%{ -> %{) throughout locale file
* Add tests for Indexa Capital provider integration
- IndexaCapitalItemTest: validations, credentials, scopes, sync status
- IndexaCapitalAccountTest: upsert, holdings, account provider linking
- Provider::IndexaCapitalTest: auth modes, API stubs, error handling
- IndexaCapitalItemsControllerTest: CRUD, setup, linking, authorization
- Fixtures for items (token + credentials) and accounts (mutual + pension)
52 tests, 98 assertions, 0 failures
* Address code review feedback from PR #933
- Fix zero balance bug: use `nil?` instead of `present?` so 0 is stored
- Fix has_indexa_capital_credentials? to check api_token (was ignored)
- Fix build_provider to delegate to Provided concern (was ignoring token)
- Fix IndexaCapital section outside encryption_error guard in settings
- Add account_number sanitization to prevent path traversal in API URLs
- Replace all skipped processor tests with real working tests
- Add zero-balance and path-traversal test coverage
61 tests, 107 assertions, 0 failures
* Address code review round 2: credentials validation, RuboCop, test quality
- Fix RuboCop SpaceInsideArrayLiteralBrackets in credentials check
- Chain where.not calls so all three username/document/password must be present
- Require all three credentials (||) instead of any one (&&) in validate_configuration!
- Move attr_reader to private to avoid exposing credentials publicly
- Parse dates with Date.parse in extract_balance for robustness
- Remove stale TODO and Crypto from supported_account_types
- Order build_provider query deterministically by created_at
- Replace no-op holdings assertion with meaningful assert_difference
* Address code review round 3: JSON parse safety and test precision
- Rescue JSON::ParserError on 2xx responses for clearer error messages
- Fix weak balance assertion: set balance to 0 before processing, assert
expected value (27093.01 = sum of holdings amounts)
* Include Indexa Capital in automatic family sync
Add indexa_capital_items to Family::Syncer#child_syncables so balances
and holdings refresh on daily auto-sync and login sync, not only on
manual sync button clicks.
---------
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
* Add account linking functionality for SnapTrade items
- Introduced UI to link existing accounts when setting up SnapTrade items, preventing duplicate account creation.
- Updated controller to fetch linkable accounts.
- Added tests to verify proper filtering of accounts and linking behavior.
* Add `snaptrade_item_id` to account linking flow for SnapTrade items
- Updated controller to allow specifying `snaptrade_item_id` when linking accounts.
- Adjusted form and views to include `snaptrade_item_id` as a hidden field.
- Enhanced tests to validate behavior with the new parameter.
- Introduced new tests to cover SnapTrade decryption and connection errors in `SnaptradeItemsControllerTest`.
- Updated error messages for improved user clarity.
- Modified `unlink` functionality to preserve `SnaptradeAccount` records while ensuring proper detachment of associated holdings.
* feat: Protect demo monitoring API key from deletion
- Add DEMO_MONITORING_KEY constant to ApiKey model
- Add `demo_monitoring_key?` method to identify the monitoring key
- Add `visible` scope to exclude monitoring key from UI queries
- Update controller to use `visible` scope, hiding the monitoring key
- Prevent revocation of the monitoring key with explicit error handling
- Update Demo::Generator to use the shared constant
Users on the demo instance can still create their own API keys,
but cannot see or delete the monitoring key used for uptime checks.
https://claude.ai/code/session_01RQFsw39K7PB5kztboVdBdB
* Linter
* Protect demo monitoring API key from deletion
* Use monitoring source for demo API key
* Add test for demo monitoring revoke guard
* Disable Rack::Attack in test and development
---------
Co-authored-by: Claude <noreply@anthropic.com>