Skip to content

feat(web): per-provider AI consent gate + coverage expansion (A-01/A-02/A-07/A-08) - #381

Merged
qnbs merged 12 commits into
mainfrom
cursor/backlog-a01-consent-enforcement-5c7a
Jul 14, 2026
Merged

feat(web): per-provider AI consent gate + coverage expansion (A-01/A-02/A-07/A-08)#381
qnbs merged 12 commits into
mainfrom
cursor/backlog-a01-consent-enforcement-5c7a

Conversation

@qnbs

@qnbs qnbs commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Works through backlog items A-01, A-02, A-07, A-08, and A-09 from the 2026-07-02 full audit report.

A-01: Per-Provider AI Consent Gate (P1 — security/UX)

Enforces data-transmission consent (GDPR Art. 6/7) before any cloud AI call to each provider. The consent is stored per-provider in localStorage; once granted, the user is not re-prompted.

Changes:

  • services/localRoutingService.ts: consent check injected into withLocalFallback — dynamically imports aiConsentService + aiProviderService, requests user consent via UIStore promise; denial routes to local AI fallback without any network request
  • stores/useUIStore.ts: providerConsentRequest state + requestProviderConsent(provider) promise action + resolveProviderConsent(granted) resolver
  • components/common/ProviderConsentModal.tsx: new modal (rendered at app root) subscribed to UIStore providerConsentRequest; shows provider display name, DPA link; i18n via settings namespace
  • components/views/plants/App.tsx: mount ProviderConsentModal at app root
  • locales/*/settings.ts: add ai.providerConsentDeny key (all 5 locales: EN/DE/ES/FR/NL)

Tests (5 new — aiConsentEnforcement.test.ts):

  • Denial falls back to local AI without cloud call
  • Grant calls cloud AI and persists consent in localStorage
  • Already-granted skips dialog and calls cloud directly
  • Uses the active provider id, not a hardcoded one
  • Cloud error after consent still falls back to local AI

A-02/A-07: analyticsService coverage expansion

analyticsService.test.ts expanded from 157 to 363 LOC (+21 meaningful tests):

  • Empty state, gardenScore weighting (health/env/problems/journal), riskFactors (health threshold, environment VPD stress), milestone estimation (harvest, transplant, finished), journalActivityTrend 14-day window counting, nutrientConsistency entry validation, growDurationStats for finished plants, multi-plant strain ranking, stage distribution accumulation

A-02/A-08: voiceCommandRegistry coverage expansion

voiceCommandRegistry.test.ts expanded (+25 tests):

  • levenshtein: identity, empty string, substitution, insertion, deletion, multi-char (kitten→sitting=3), symmetry, unicode cannabis terms
  • matchVoiceCommand pass 1 (exact alias), pass 2 (fuzzy Levenshtein ≤2), pass 3 (keyword tokens ≥2), edge cases (empty registry, short tokens), requiresConfirmation passthrough

A-09: CRDT conflict UI verified

SyncConflictModal is fully wired in CloudSyncPanel: setSyncConflict(info, null)syncState.conflictInfo → modal renders with 3 resolution paths (merge, keep local, keep remote). No code changes needed — wiring confirmed complete.

chore: file-budget locale exclusion

scripts/check-file-budget.mjs diff-based check now excludes locales/ and data/strains/ paths — these are translation dictionaries and content files, not architecture-bounded modules. The SCAN_GLOBS (advisory full scan) already excluded these paths; the changed-file gate now matches.

Security Impact

  • Security-sensitive paths touched
  • Secret, dependency, or workflow changes included

Details: withLocalFallback now checks aiConsentService.hasProviderConsent(provider) before every cloud AI call. Denial routes to local AI — no data leaves the device without explicit per-provider user consent.

i18n

  • i18n keys added for all 5 languages (EN/DE/ES/FR/NL) — ai.providerConsentDeny added to all 5 locale settings files
  • No hardcoded user-facing strings in .tsx files

Validation

Gate Result
pnpm run typecheck ✅ 5/5 tasks
pnpm run lint:changed ✅ 0 errors
pnpm run check:file-budget ✅ 0 failures (with locale exclusion fix)
pnpm run test:run ✅ 275 files, 2931 tests
pnpm audit ✅ 0 vulnerabilities
Pre-push hook ✅ all gates passed

Notes

  • Backlog items addressed: A-01 (P1 — done), A-02 (P1 — partial, coverage raised), A-07 (P2 — done), A-08 (P2 — done), A-09 (P2 — verified complete)
  • Next backlog items: A-04 (Recharts a11y), coverage toward 50/50/35/50 roadmap targets
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Added a global provider consent modal with provider-specific messaging and an optional DPA link.
    • Implemented per-provider consent gating so cloud AI requests only proceed after approval; denial uses local AI fallback.
    • Extended consent denial wording across English, German, Spanish, French, and Dutch.
  • Tests

    • Added unit tests for consent enforcement, including local fallback and consent-grant/deny scenarios.
    • Reworked analytics, local routing, voice command matching, and UI store tests to improve coverage and reliability.

…aise coverage floors

- docs: SECURITY.md now lists v1.9.x as supported (was stale at 1.8.x)
- deps: add pnpm.overrides @babel/core >=7.29.6 (GHSA-4x5r-pxfx-6jf8 — arbitrary
  file read via sourceMappingURL comment; devDep path via Stryker instrumenter)
  pnpm audit now reports 0 vulnerabilities across all severity levels
- test: raise vitest coverage thresholds to measured values — lines 43%, functions 41%
  (Stufe C; all 4 critical-path files confirmed >=80%: safetyPipeline 100%,
  syncEncryptionService 96.77%, plantSimulationService 98.5%, diagnosisService 96.1%)
- docs: AUDIT-REPORT-2026-07-02-FULL.md — full-scale deep audit and perfection report
  (2936 tests, all quality gates green, maturity score 8.9/10, backlog A-01 to A-12)
- docs: CHANGELOG unreleased section updated with post-v1.9.0 splits and ci improvements

Gates verified: typecheck pass, lint:changed pass, file-budget pass, pnpm audit 0 vulns
Enforce data-transmission consent (GDPR Art. 6/7) before the first cloud
AI call to each provider. Consent is stored per-provider in localStorage;
once granted it is not re-prompted.

- services/localRoutingService.ts: consent check in withLocalFallback —
  dynamically imports aiConsentService + aiProviderService; requests
  consent via UIStore promise; denial falls back to local AI
- stores/useUIStore.ts: add providerConsentRequest state +
  requestProviderConsent(provider) promise-based action +
  resolveProviderConsent(granted) resolver
- components/common/ProviderConsentModal.tsx: new modal subscribed to
  UIStore providerConsentRequest; renders provider display name + DPA link
  from aiConsentService; all 5 locales via settings namespace
- components/views/plants/App.tsx: mount ProviderConsentModal at app root
- locales/*/settings.ts: add ai.providerConsentDeny key (all 5 locales)

Tests:
- services/aiConsentEnforcement.test.ts: 5 new tests — deny falls back to
  local, grant calls cloud + persists consent, already-granted skips dialog,
  active-provider used (not hardcoded), cloud error after consent falls back
- services/localRoutingService.test.ts: add consent/provider/UIStore mocks
  so existing routing tests continue to pass unaffected

Gates: typecheck pass, lint:changed pass (10 files), 16/16 tests pass
…(A-02/A-07/A-08)

analyticsService.test.ts (157 -> 363 LOC, +21 tests):
- empty state, gardenScore weighting, riskFactors health/environment,
  milestone estimation harvest/transplant/finished, journalActivityTrend
  14-day buckets and entry counting, nutrientConsistency entries,
  growDurationStats, multi-plant strain ranking, stage distribution

voiceCommandRegistry.test.ts (+25 tests):
- levenshtein: identity, empty, substitution, insertion, deletion,
  multi-char edit distance, symmetry, unicode cannabis terms
- matchVoiceCommand pass 1 (exact alias), pass 2 (fuzzy <= 2),
  pass 3 (keyword tokens >= 2), edge cases, requiresConfirmation passthrough

A-09 CRDT conflict UI verified complete: SyncConflictModal wired in
CloudSyncPanel with setSyncConflict -> syncState.conflictInfo -> modal
(3 resolution paths: merge, keep local, keep remote). No code changes needed.

Gates: typecheck pass, 46/46 tests pass
…d-file check

These are translation data files and strain catalog content — the 700 LOC
architecture budget applies to component/service/hook modules, not locale
dictionaries or strain data files which are inherently large by design.

The SCAN_GLOBS (used for the advisory full scan) already excluded these paths;
the diff-based check (used in pre-push and CI changed-files gate) now matches.
@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
canna-guide-2025-web Ready Ready Preview, Comment Jul 14, 2026 4:16pm

@github-actions github-actions Bot added documentation Improvements or additions to documentation security tests labels Jul 2, 2026
@qnbs
qnbs marked this pull request as ready for review July 14, 2026 12:18
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 515b3654-4589-4215-aa28-c1f42c130e8e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a global per-provider AI consent flow with local fallback enforcement, provider-specific modal content, and translations. It also expands analytics service tests and refocuses voice command tests on pure matching utilities.

Changes

Provider consent flow

Layer / File(s) Summary
Consent state and resolution contract
apps/web/stores/useUIStore.ts, apps/web/stores/useUIStore.test.ts
Adds pending provider consent state and actions that create, resolve, deduplicate, queue, and clear consent requests, with concurrent-request coverage.
Cloud-call consent enforcement
apps/web/services/localRoutingService.ts, apps/web/services/aiConsentEnforcement.test.ts, apps/web/services/localRoutingService.test.ts
Checks consent for the active provider before cloud calls, centralizes cloud-disabled and rate-limit handling, uses local fallback on denial, and tests granted, denied, existing, and failed-call paths.
Consent modal and app integration
apps/web/components/common/ProviderConsentModal.tsx, apps/web/components/views/plants/App.tsx, apps/web/locales/*/settings.ts
Adds the global provider consent modal, mounts it in the app, and supplies denial translations.

Analytics service test coverage

Layer / File(s) Summary
Analytics result coverage
apps/web/services/analyticsService.test.ts
Adds shared plant fixtures and assertions for analytics collections, milestones, risks, trends, nutrients, durations, and multi-plant aggregation.

Voice command matcher tests

Layer / File(s) Summary
Matcher utility coverage
apps/web/services/voiceCommandRegistry.test.ts
Refocuses tests on Levenshtein distance and command matching with exact, fuzzy, keyword, edge-case, and confirmation-flag coverage.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CloudRouting as withLocalFallback
  participant ProviderService as aiProviderService
  participant ConsentService as aiConsentService
  participant UIStore as useUIStore
  participant ConsentModal as ProviderConsentModal
  CloudRouting->>ProviderService: getActiveProvider()
  CloudRouting->>ConsentService: hasConsent(provider)
  CloudRouting->>UIStore: requestProviderConsent(provider)
  UIStore->>ConsentModal: expose consent request
  ConsentModal->>UIStore: resolveProviderConsent(granted)
  UIStore-->>CloudRouting: return consent result
  CloudRouting->>ConsentService: grantConsent(provider)
  CloudRouting->>ProviderService: execute cloud call
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a per-provider AI consent gate plus expanded coverage.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/backlog-a01-consent-enforcement-5c7a

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

This branch predates the pnpm 11 migration (#413) by 37 commits, so it still
carried package.json's `pnpm.overrides` block -- which pnpm 11 no longer reads.
Its only addition there, `@babel/core: >=7.29.6`, already lives on main in
pnpm-workspace.yaml, so package.json and the lockfile take main's version.

check-file-budget.mjs also takes main's version. Both branches hit the same
problem -- touching a locale file failed the budget gate -- and solved it
differently: this branch blacklisted `locales/` and `data/strains/`, main scoped
the changed-file path to SCAN_GLOBS. The whitelist subsumes the blacklist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@deepsource-io

deepsource-io Bot commented Jul 14, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 2dbc194...6710636 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

Important

Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
Docker Jul 14, 2026 2:47p.m. Review ↗
JavaScript Jul 14, 2026 2:47p.m. Review ↗
Python Jul 14, 2026 2:47p.m. Review ↗
Rust Jul 14, 2026 2:47p.m. Review ↗
Shell Jul 14, 2026 2:47p.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

Comment thread apps/web/services/aiConsentEnforcement.test.ts Outdated
Comment thread apps/web/services/aiConsentEnforcement.test.ts Outdated
Comment thread apps/web/services/aiConsentEnforcement.test.ts Outdated
Comment thread apps/web/services/aiConsentEnforcement.test.ts Outdated
Comment thread apps/web/services/localRoutingService.test.ts Outdated
Comment thread apps/web/services/localRoutingService.ts Outdated
Comment thread apps/web/services/voiceCommandRegistry.test.ts Outdated
…aller

requestProviderConsent() stored the pending resolver in the store and returned a
fresh promise each time. Two AI calls reaching the gate before the user answers
-- two analyses in flight, or a StrictMode double-invoke -- meant the second call
overwrote the first one's resolver. resolveProviderConsent() then settled only
the second. The first promise stayed pending forever and its AI call sat in a
loading state that nothing could ever clear.

A caller arriving while a prompt is open now joins it instead of replacing it,
and both settle on the single answer the user gives. Covered by two tests, which
hang against the previous implementation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qnbs

qnbs commented Jul 14, 2026

Copy link
Copy Markdown
Owner Author

Rebased onto main (37 commits behind) and reviewed.

Merge

Three conflicts, all from this branch predating the pnpm 11 migration (#413):

  • package.json / pnpm-lock.yaml — this branch still carried the pnpm.overrides block, which pnpm 11 no longer reads. Its one addition there, @babel/core: >=7.29.6, already lives on main in pnpm-workspace.yaml, so both files take main's version.
  • check-file-budget.mjs — both branches hit the same problem (touching a locale file failed the budget gate) and solved it differently: this branch blacklisted locales/ and data/strains/, main (ci: enforce that every workspace declares what it imports #423) scoped the changed-file path to SCAN_GLOBS. The whitelist subsumes the blacklist, so main's version wins.

Both new gates on main pass against this branch: check:phantom-deps clean, check:file-budget clean.

Review of the consent gate

The placement is right. withLocalFallback is the single funnel for cloud calls (runRoutedwithLocalFallback, and geminiService is reached from nowhere else), and the gate sits after the local/eco early-return and before cloudFn(). So a local-only user is never prompted, and a denial returns the local fallback without a single network request. Consent is persisted per provider, so no re-prompting.

One bug found and fixed (ac374d9)

requestProviderConsent() stored the pending resolver in the store and returned a fresh promise on every call:

requestProviderConsent: (provider) =>
    new Promise<boolean>((resolve) => {
        set({ providerConsentRequest: { provider, resolve } })
    }),

Two AI calls reaching the gate before the user answers — two analyses in flight, or a StrictMode double-invoke — meant the second overwrote the first one's resolver. resolveProviderConsent() then settled only the second. The first promise stayed pending forever, and its AI call sat in a loading state nothing could ever clear.

A caller arriving while a prompt is open now joins it rather than replacing it, and both settle on the single answer the user gives. Two tests cover it; they hang against the previous implementation. useUIStore + aiConsentEnforcement: 39 tests green.

🤖 Generated with Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/components/common/ProviderConsentModal.tsx`:
- Around line 17-23: Update the translation lookups in ProviderConsentModal to
use the security.* keys instead of ai.* for the provider consent title, deny
action, prompt, and DPA link. Keep the existing settings namespace and
interpolation behavior unchanged while switching each affected t call to the
matching security locale entries.

In `@apps/web/locales/es/settings.ts`:
- Line 79: Update ProviderConsentModal.tsx to read all consent modal strings
from the security namespace: security.providerConsent,
security.providerConsentDeny, security.providerConsentPrompt, and
security.providerDpaLink. Apply the corresponding locale-key correction in
apps/web/locales/es/settings.ts (79-79), apps/web/locales/fr/settings.ts
(79-79), and apps/web/locales/nl/settings.ts (77-77), ensuring the title,
prompt, and buttons resolve consistently.

In `@apps/web/services/analyticsService.test.ts`:
- Around line 267-301: The getNutrientConsistency test currently places nutrient
values in journal entries, but getNutrientConsistency reads plant.medium.ph and
plant.medium.ec. Update the plantWithReadings fixture to provide the intended pH
and EC values through its medium field, while preserving the assertions that
validate the resulting consistency entry.

In `@apps/web/services/localRoutingService.ts`:
- Around line 114-127: Wrap the entire per-provider consent gate in
withLocalFallback’s existing error-handling path, including getActiveProviderId,
the useUIStore import, requestProviderConsent, and grantProviderConsent, so any
thrown error invokes localFallback() rather than escaping. Preserve the current
denied-consent fallback and granted/already-granted behavior.
- Around line 115-119: Update the consent flow in withLocalFallback around
aiProviderService.getActiveProviderId and useUIStore.requestProviderConsent to
deduplicate in-flight requests per provider. Ensure overlapping calls for the
same activeProvider await the same promise or queued resolver without
overwriting an earlier caller, while preserving independent consent handling for
different providers.

In `@apps/web/services/voiceCommandRegistry.test.ts`:
- Around line 2-7: Update the module docstring in the voiceCommandRegistry test
file to remove the inaccurate claim that buildVoiceCommands is tested via a mock
fixture, leaving only coverage statements for the utilities actually imported
and tested, such as levenshtein and matchVoiceCommand.

In `@apps/web/stores/useUIStore.ts`:
- Around line 65-69: Implement a FIFO queue for provider-consent requests
instead of storing only one pending request. Update the providerConsentRequest
state and requestProviderConsent, plus the resolution logic around the affected
consent handlers, so concurrent callers retain each resolve callback, are
presented one at a time, and each promise settles exactly once before advancing
to the next queued request. Preserve the existing behavior for a single request
and update related state typing or documentation to reflect the queue.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 85163e61-5598-4dd2-9fce-3950b122d582

📥 Commits

Reviewing files that changed from the base of the PR and between 7e203cf and 5af25df.

📒 Files selected for processing (13)
  • apps/web/components/common/ProviderConsentModal.tsx
  • apps/web/components/views/plants/App.tsx
  • apps/web/locales/de/settings.ts
  • apps/web/locales/en/settings.ts
  • apps/web/locales/es/settings.ts
  • apps/web/locales/fr/settings.ts
  • apps/web/locales/nl/settings.ts
  • apps/web/services/aiConsentEnforcement.test.ts
  • apps/web/services/analyticsService.test.ts
  • apps/web/services/localRoutingService.test.ts
  • apps/web/services/localRoutingService.ts
  • apps/web/services/voiceCommandRegistry.test.ts
  • apps/web/stores/useUIStore.ts

Comment thread apps/web/components/common/ProviderConsentModal.tsx
Comment thread apps/web/locales/es/settings.ts
Comment thread apps/web/services/analyticsService.test.ts
Comment thread apps/web/services/localRoutingService.ts Outdated
Comment thread apps/web/services/localRoutingService.ts Outdated
Comment thread apps/web/services/voiceCommandRegistry.test.ts
Comment thread apps/web/stores/useUIStore.ts
qnbs and others added 4 commits July 14, 2026 15:56
My previous commit reached for `useUIStore.getState()` inside the store's own
creator and *returned* a value derived from it. That makes the store's type
circular: TypeScript gives up inferring UIState, and every
`useUIStore((s) => ...)` selector in the app collapses to `any` -- 40+ TS7006
and TS18046 errors across components that never touched consent.

Zustand hands the creator a `get` for exactly this. Signature is now
`(set, get)`, and both consent actions read through it.

The four remaining `useUIStore.getState()` calls in this file predate this branch
and are fine: they return void, so nothing circular flows back into the type.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Branch coverage came in at 24.99% against a 25% threshold -- and the branches
that dragged it under were mine: the race fix added a queue path for a second
provider that no test exercised.

Covered now. The test also pins the behaviour down: the queued request opens its
own prompt once the first is answered, and each caller gets the answer given to
its own prompt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t gate

Two of these were production bugs.

**The consent gate escaped its own fallback contract.** The try/catch wrapped
only cloudFn(), but the gate ahead of it does I/O: two dynamic imports, a
provider lookup and an awaited prompt. Any of those throwing propagated straight
out of withLocalFallback -- breaking the guarantee the function documents, that
the user always gets a response. The gate now sits inside the try, so a failing
import or a rejected prompt lands in the local fallback like any other failure.
Two tests cover it; both fail against the previous code.

**ProviderConsentModal rendered raw keys.** It looked the strings up as
`t('ai.providerConsent', { ns: 'settings' })`, but they live under `security.*`
in the settings locale. The modal would have shown the literal key strings to
every user it prompted. All four lookups corrected.

**The nutrient-consistency test asserted nothing.** getNutrientConsistency()
reads plant.medium.ph / .ec; the fixture only set journal entries, which the
function never consults. The test therefore passed or failed on basePlant.medium,
not on the values it appeared to be testing. Rewritten against `medium`, with
exact assertions on avgPh/avgEc/variance/rating, plus an unstable-drift case.

Also:
- withLocalFallback: complexity 10 (DeepSource JS-R1005). The consent gate is
  extracted into ensureProviderConsent(), which is what pushed it over.
- Function declarations in global scope (JS-0067): withLocalFallback, makeCmd and
  importWithLocalFallback are now const arrows.
- async without await (JS-0116): five mock factories no longer claim to be async.
- voiceCommandRegistry.test.ts claimed to cover buildVoiceCommands, which it never
  imports. Docstring now says what the file actually tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread apps/web/services/localRoutingService.ts
qnbs and others added 2 commits July 14, 2026 16:51
…udget

DeepSource JS-R1005: still 9 after extracting the consent gate. The mode check
and the rate-limit notification are both self-contained and neither is about
falling back, so they move out too -- isCloudDisabled() and notifyIfRateLimited().

What is left reads as the contract it implements: skip the cloud in local modes,
ask for consent, call the cloud, and fall back to local on any failure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 'Analyze (python)' job died in 'Initialize CodeQL' with
'Error: read ECONNRESET' -- a network failure on GitHub's side, not a finding.
The run sits on the current HEAD and GitHub refuses to retry it, so this empty
commit is the only way to get a fresh one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qnbs

qnbs commented Jul 14, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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

@qnbs
qnbs merged commit be856fc into main Jul 14, 2026
28 checks passed
@qnbs
qnbs deleted the cursor/backlog-a01-consent-enforcement-5c7a branch July 14, 2026 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation security tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants