Activity Nexus — PRD (Penny-proposed, Josh-greenlit)
Product: Objectuve (codename: Enkidu) Feature bundle: Connected Apps integration hub (Strava + Chess.com) with auto-mapping and idempotent check-ins Status: Shipped — built and merged to master 2026-08-03 (v4.14, narrative); not yet live to users — connected_apps_enabled flag at 0% pending Strava provisioning (B1–B3) Date: 2026-04-30 Proposer: Penny (Product Lead, feasibility study OBJ-44) PRD file: docs/product/completed/activity-nexus-prd.mdConfidence: Medium (concept strong, API landscape narrower than original ask)
1. Problem Statement
Users who already track fitness on Strava or hold streaks on Chess.com must re-enter the same data as Objectuve habit check-ins. Every redundant log-in is friction that breaks streaks and removes the people most likely to thrive — those already building activity habits elsewhere — from Objectuve's daily loop.
Specific gaps:
- Strava users log a 10-mile run but must also check a "Morning Run" habit in Objectuve to count toward their goal. Two systems, two taps, one source of truth.
- Chess.com players rack up games but don't link that activity to their "Play Chess" streak in Objectuve.
- Duolingo streaks (the original ask) have no official API and present ToS risk — not feasible via public channels.
- Fitbit is deprecated September 2026; the direct replacement is Google Health API, which Objectuve doesn't yet support.
The core opportunity: eliminate the double-entry by importing activity data and auto-checking the matching habits. This surfaces as a first-class "Connected Apps" surface, not a one-off integration.
2. Goals & Success Metrics
Primary Goals
- Reduce friction for multi-tracker users — users who already log elsewhere should not have to re-enter data in Objectuve to maintain streaks.
- Increase goal completion rate (north star) among users with active integrations by automating check-ins they would manually enter anyway.
- Build integration framework for future providers — first with Strava + Chess.com (MVP), but designed reusable for Apple Health, Google Health, Garmin (post-partnership) without rewrites.
- Demonstrate integration value for roadmap prioritization — if adoption is strong, integrations move from Year 3 to Phase 6 (concurrent with Teams monetization).
Success Metrics
| Metric | Target | Measurement |
|---|---|---|
| % of users who connect at least one provider | 8–15% (typical onboarding feature adoption) | Connected apps / active users |
| % of connected-app activities auto-checked | 85%+ | Auto-checked / total eligible activities (Strava runs, Chess.com games) |
| Avg goal completion rate (connected users vs. control) | +8–12pp | Cohort comparison post-launch |
| % of users who disconnect within 30 days | <20% (low churn = trust in mapping) | Disconnections / connections at 30d mark |
| Activity feed impression ("via Strava" marker) | 50%+ of daily active users see it | Analytics on activity_feed events tagged source: integration |
| Strava OAuth completion rate | 75%+ (Capacitor browser handoff) | OAuth grants / OAuth start attempts |
Guardrails
- Free tier feature — no paywall. Activity integration is free for all users. (See MISSION.md value #3, #5.)
- Auto-check-in is confident, not silent — first connect surfaces explicit mapping review; subsequent new habits in matching categories are proposed (toast + link to settings), never silently checked in.
- Credential security — encrypted token storage (Rails 7+
ActiveRecord::Encryption); tokens never logged or exposed in error messages. - ToS compliance — Strava GPS data requires Privacy Policy + ToS updates before any public launch. Internal/QA use allowed pre-update.
- Idempotent check-ins — same activity can't double-check a habit. Check-in keyed on
(goal_id, completed_date, integration_provider_id)uniqueness. - Graceful degradation — if a provider API is down, no check-ins are queued; users see a "Connection paused" message in settings, not an error on Dashboard.
3. User Stories
- As a user with active Strava, I want to authorize Objectuve to read my activities, so that my fitness runs automatically check my habits without me manually re-entering.
- As someone new to Connected Apps, I want to review a confidence-scored mapping of my Strava activities to my habits before confirming, so I trust the auto-check-ins and don't worry about mismatches.
- As a user who built a Chess.com streak, I want my daily games to auto-check my "Play Chess" habit, so I maintain my Objectuve streak without extra work.
- As a user concerned about privacy, I want to see exactly what data Objectuve imports and disconnect any time, so I control my integration footprint.
- As a user whose integration breaks, I want a clear "Connection paused" message in settings, not a silent failure, so I can troubleshoot and reconnect.
4. Feasibility & API Landscape (Penny's Research)
Feasibility Score: 7 / 10
| Dimension | Score | Notes |
|---|---|---|
| Technical complexity | 6/10 | OAuth flows + data normalization are real work; no integration framework exists yet in the Rails API |
| API availability | 7/10 | Strava and Chess.com are easy wins; Fitbit is deprecated Sept 2026; Duolingo has no official API |
| Market value | 9/10 | Integration hubs are proven retention drivers — reduces double-logging friction |
| North-star alignment | 9/10 | Auto check-ins → habits completed → goals completed. Direct. |
| Enkidu fit | 7/10 | Habits + recurrence are already there; need: integration framework, OAuth UI, auto-check-in logic |
| Roadmap timing | 5/10 | Integrations are Year 3; pulling forward is a deliberate choice |
Supported Providers (MVP)
| Provider | Status | Verdict | MVP Priority |
|---|---|---|---|
| Strava | ✅ OAuth 2.0, free tier, well-documented | Webhook-driven, real-time activity ingestion | MVP |
| Chess.com | ✅ Public REST API, no auth required | Daily polling, zero auth friction | MVP |
| Google Health API | ✅ Active — correct Fitbit replacement | Post-MVP (framework supports it) | Phase 2 |
| Apple HealthKit | ✅ iOS native via Capacitor | Post-MVP | Phase 2 |
| Fitbit | ⚠️ Deprecated Sept 2026 | Skip; use Google Health | Not supported |
| Garmin Connect | ⚠️ Business partnership required | Post-MVP | Phase 3 |
| Duolingo | ❌ No official API, ToS risk | Do not build (use Chess.com instead) | Not supported |
Critical finding (Penny): Duolingo has no official public API. Reverse-engineered community packages exist but violate Duolingo's ToS and are unsupported/fragile. Decision (Josh): Accept Chess.com as the streak-app substitute — fully public, zero auth, zero ToS risk.
5. MVP Scope (Josh's Direction)
What Ships in MVP
5.1 Strava Integration
- OAuth 2.0 authorization (Capacitor in-app browser, callback via deep link)
- Webhook subscription (real-time activity ingestion from Strava)
- Activity normalization (
Activitydomain model with type, duration, distance, timestamp) - Mapping: Strava
activity_type(run, cycle, swim, etc.) → Objectuvegoal_category(fitness)
5.2 Chess.com Integration
- Public REST API adapter (no auth)
- Daily poller (Crono job at 02:00 UTC, fetches previous day's games)
- Activity normalization (game count, timestamp)
- Mapping: "player has games on date X" → Objectuve
goal_category(games)
5.3 Smart Auto-Mapping Engine
- Confidence-scored proposals on first connect (≥0.85 auto-check, 0.50–0.85 propose, <0.50 ignore)
- Mapping review modal — user accepts, edits, or dismisses proposed matches
- One-tap mapping management — Settings → Connected Apps → [Provider] → Manage Mappings
- Toast notification on auto-map — "New habit detected: [habit name]. Linked via Strava. [Edit]"
5.4 Connected Apps Settings Surface
- List of connected providers with status (connected, paused, error)
- OAuth handoff for each provider
- Disconnect (with confirmation)
- View mapping details per provider
- Re-run mapping review
5.5 Activity Feed Integration
- Imported activities visibly attributed — "Checked in via Strava" pill on
GoalEventCard GoalEvent#sourceenum (manual|integration) to distinguish user-entered from imported- Analytics: track impression + tap-through rate on provider attribution
5.6 Documentation & ToS
- User-facing docs:
docs/features/connected-apps.md(how to connect, what data we access, how to disconnect) - Privacy Policy update (Termly): section on activity data import from Strava
- ToS update (Termly): clause on third-party data ingest and retention
- Release notes highlighting integration as free feature
What's Out of Scope (MVP)
- Apple Health, Google Health, Garmin — Phase 2+ (framework designed for them, not built)
- Bidirectional sync — Objectuve → Strava (sync check-ins back to Strava). One-way import only.
- Goal progress updates — Only habits auto-check-in; goal progress (% toward monthly target) stays manual for now
- Custom activity types — Only Strava's built-in types + Chess.com games mapped; no user-defined "other activity" type
- Retroactive sync — Sync starts from connection date; historical data before connection date is not imported
6. Feature Design (High-level — UI-SPEC After Greenlight)
6.1 OAuth Flow (Strava)
- Settings → Connected Apps → "Connect Strava"
- Tap →
Capacitor.Browser.open(strava_oauth_authorize_url)(in-app browser) - User logs into Strava, grants permission
- Redirect to
objectuve://strava/callback?code=...&state=... - Capacitor browser closes
- Deep-link handler exchanges code for access token (backend POST
/integrations/strava/authorize) - Mapping review modal surfaces (unless user opts to skip)
- Token stored encrypted in
IntegrationConnectionrecord
6.2 Mapping Review Modal (First Connect)
- Header: "Link your Strava activities to habits"
- Proposed mappings table: (confidence score, activity type, → Objectuve habit, [Accept] [Edit] [Dismiss])
- Help text: "High confidence (85%+) means we're confident; medium confidence means double-check this. You can change mappings anytime in settings."
- Buttons: [Accept All] [Custom] (deep dive per mapping)
- Confirmation state: "✓ 3 mappings linked. Your fitness activities will auto-check habits."
6.3 Connected Apps Settings Surface
List view:
- Provider card per connected provider
- Status badge (Connected / Paused / Error / Authorizing)
- "Last sync: 2 hours ago" timestamp
- [Disconnect] button (confirmation modal)
- [Manage Mappings] link
Manage Mappings view:
- Table: Strava activity type | Auto-check habit | [Edit] [Remove]
- [+ Add Mapping] button
- [Re-run Auto-Mapping] button (surfaces new mapping suggestions)
6.4 Activity Feed Attribution
- Event card shows: "You checked in: Morning Run (via Strava) — 10 miles, 58 min"
- Strava pill color: Strava orange (
#fc5200), subtle background - Chess.com pill: Chess.com gradient, subtle background
- Tap pill → attribution details (activity timestamp, synced data)
6.5 Disconnection & Error States
- Disconnect: Confirm modal — "Remove Strava connection? Past check-ins stay, but future activities won't sync."
- Paused state: "Your Strava connection paused. [Reconnect]" (user reconnects via OAuth, not manual token re-entry)
- API error: "Strava sync failed. Check your connection and try [Retry]. Or [Disconnect]."
7. Technical Overview (Orion's Epic Plan)
7.1 Data Model
IntegrationProvider
- provider_slug (Strava, Chess.com, GoogleHealth, etc.)
- name, logo_url, description
- auth_type (oauth, api_key, none)
IntegrationConnection
- user_id, provider_id
- access_token, refresh_token, token_expires_at (encrypted)
- status (active, paused, error)
- synced_at (last successful activity fetch)
HabitIntegrationMapping
- user_id, integration_connection_id, goal_id (habit)
- provider_activity_type (Strava.run, Chess.com.game, etc.)
- confidence_score (0–1, used during proposal)
- auto_check_enabled (boolean, user can disable per mapping)
IntegrationActivity
- integration_connection_id, provider_activity_id, goal_id
- activity_type, duration_minutes, distance_km, timestamp
- checked_at (when auto-check-in happened, if any)
- idempotency_key (provider_id, provider_activity_id, date) → prevents double-checks7.2 Adapter Pattern
# lib/integration_provider/adapter.rb (abstract base)
class IntegrationProvider::Adapter
def authorize_url(state:) # OAuth start → raises NotImplementedError
def exchange_code(code:) # OAuth callback, returns tokens
def fetch_recent_activity(connection:) # Returns array of normalized activities
def verify_signature(request) # Webhook security (Strava specific)
end
# lib/integration_provider/strava.rb
class IntegrationProvider::Strava < IntegrationProvider::Adapter
# Implementation of above methods
end7.3 Interaction Layers
Integrations::AuthorizeConnection
- Takes: provider_slug, oauth_code, user_id
- Returns: IntegrationConnection record
Integrations::FetchAndMapActivities
- Polls provider API or receives webhook
- Calls Integrations::MapIncomingActivity for each activity
- Calls Integrations::AutoCheckIn if confidence ≥ threshold
Integrations::MapIncomingActivity
- AI-powered: analyzes activity description + user's habits
- Returns: confidence score + proposed habit match
Integrations::AutoCheckIn
- Wrapper around GoalTracking::CheckInHabit
- Idempotent on (goal_id, completed_date, integration_provider_id)
- Publishes domain event: integrations.activity_checked_in7.4 Strava Webhook Receiver
POST /webhooks/strava
- Signature verification (Strava HMAC-SHA256)
- Parse activity subscription event
- Enqueue `IngestionJob` (async)
- Return 200 OK immediately7.5 Chess.com Polling Job
# Crono schedule: 02:00 UTC daily
class IntegrationProvider::Chess::PollActivityJob
- For each connected Chess.com user:
- Fetch user's public games from "yesterday" (user's local timezone)
- Dedupe vs. IntegrationActivity records
- Call Integrations::FetchAndMapActivities7.6 Confidence Scoring Algorithm
First-pass scoring (heuristic-based):
- Rule 1: activity_type matches goal_category — graded by match quality, not binary:
- Exact match (activity_type maps 1:1 to the goal's category, e.g. Strava
run/ride/swim/walk, or any Chess.comchess_<time_class>game) → 0.80 confidence - Partial/family match (activity_type only loosely implies the category, e.g. a generic Strava
workout) → 0.55 confidence - No relationship → 0.0
- Exact match (activity_type maps 1:1 to the goal's category, e.g. Strava
- Rule 2: Activity timestamp matches user's typical check-in time → +0.05
- Rule 3: Goal has existing check-ins from same day → +0.05 (but capped at 0.95, avoids double-entry)
- Rule 4: User has historically checked this habit on this day of week → +0.05
A bare exact category match with no other corroborating signal (e.g. a user's first-ever synced activity, before any check-in history exists) lands at 0.80 — surfaced for one-tap review, not silently auto-checked. This is intentional: "confident, not silent" (see A2 below) means the flagship first-connect review experience only fires once corroboration, not just a category match, earns auto-check.
Thresholds (tunable post-launch):
- ≥ 0.85 → Auto-check without user review
- 0.50–0.85 → Propose via toast notification + modal
- < 0.50 → Ignore (don't surface or check)
Tuning: Post-launch, track false-positive rate (user edits / deletes auto-checks) and adjust thresholds in IntegrationConfiguration table.
8. Assumptions (Call Out If Wrong)
A1. Activity Nexus is a first-class product surface, not a bolt-on. The Phase 1 framework is reusable for future providers without architectural rewrites.
A2. Smart auto-mapping = confidence-scored proposal with accept/edit/dismiss, NOT silent zero-touch auto-mapping. First connect surfaces explicit review; new habits are proposed (toast), never silently checked.
A3. Strava uses webhooks for real-time activity ingestion. Chess.com uses daily poller. No new infra component needed; existing Sidekiq + Crono sufficient.
A4. Encrypted credential storage uses Rails 7+
ActiveRecord::Encryption(no third-party gem). Keys via Rails credentials / GCP Secret Manager.A5.
goal_categorytable will get a newgamesrow (or similar) via seed migration to host Chess.com mappings. Fitness category already exists.A6. Auto-check-ins reuse
GoalTracking::CheckInHabitinteraction (idempotent on(goal_id, completed_date)). We tag the resultingGoalEventwithsource: integrationso the feed can show "via Strava."A7. User's
time_zoneis reliably populated (set in onboarding wizard or migrated from legacy system). If unset, Chess.com polling falls back to UTC with a TODO comment (precedent:GeneratePartnerMissedDayAlertsJob).
9. Open Questions (Resolve Before Phase 1 Dispatch)
| Question | Resolution path | Blocker? |
|---|---|---|
| Strava rate limits (200 req/15min, 2000 req/day on free tier) — becomes constraint at ~5K connected users. | Apply for "increased rate limits" before public launch (tracked in Phase 6 release task). | No — document as A3 in release notes |
Chess.com timezone handling — API returns UTC months; we compute "yesterday" in user's local TZ. Is User#time_zone always set? | Verify onboarding sets it; if not, add fallback (UTC + TODO). Phase 3 task includes guard. | No — has fallback |
| Confidence threshold tuning (currently 0.85/0.50/<0.50) — should these numbers be different? | Post-launch, monitor false-positive rate (user-deleted auto-checks) and adjust. Phase 4 task makes them constants. | No — has post-launch adjustment plan |
| iOS deep-link callback (Capacitor in-app browser closes on custom URL scheme). | Confirm @capacitor/browser handles objectuve://strava/callback. Risk: low (well-trodden pattern). | No — Phase 2 task includes verification |
| Strava redirect_uri for OAuth (sandbox vs. prod). | Use ENV['STRAVA_REDIRECT_URI'] (different per environment). Phase 1 task includes configuration. | No — standard OAuth practice |
10. Risks & Mitigations
| Risk | Severity | Mitigation |
|---|---|---|
| Mis-mapped auto-check-ins erode trust | High | Confidence threshold + mandatory user confirmation on first connect. Never silent. |
| Webhook spoofing (Strava) | High | Strava signs webhooks; Phase 1 task includes signature verification. Reject unsigned events with 401. |
| Token leak via logs | High | Use Rails encrypted attributes AND filter :access_token, :refresh_token in logging config. Phase 1 includes this. |
| API credentials in Git | High | All secrets via config/credentials.yml.enc and GCP Secret Manager. Never commit plaintext. |
| Strava ToS breach (GPS data) | Medium | Phase 6 release task gates feature behind Privacy Policy + ToS update (Termly required). |
| Integration breaks post-launch, user confusion | Medium | Clear "Connection paused" status + [Reconnect] button. No silent failures. |
| Rate limit exceeded, integrations queue silently | Medium | Graceful degradation: failed fetches log warning + emit alert (Sentry), do not queue check-ins. |
| User deletes habit after mapping, integration tries to check non-existent goal | Low | AutoCheckIn validates goal exists; fails gracefully with warning log, does not fatal. |
| Timezone ambiguity (Chess.com) | Low | Fallback to UTC if User#time_zone unset. Documented in code comment. |
11. Feature Design (PBC & Brand Check)
Free Tier Impact
✅ Extends free tier. Activity integration is fully free. No paywall, no gating to Supporter tier.
Engagement vs. Goals (North Star)
✅ Targets goals-completed (the north star) by automating check-ins users would manually enter anyway. Does NOT increase session length or notification volume. One-tap activity discovery, then users leave.
Dark-Pattern Check
✅ Confirmed clean. Auto-check-ins only happen for activities the user already created in another app (Strava run, Chess.com game). Not a phantom engagement hook. Attribution ("via Strava") is transparent.
Anti-Addictive Philosophy
✅ Aligns. Reduces the need to open Objectuve. Users with active integrations open less because progress happens automatically — the goal is fewer sessions, same or better outcomes.
12. Phase Breakdown (6 Phases, 6–10 Weeks AI Execution)
| Phase | Title | Owner Mix | Outcome | Estimated size |
|---|---|---|---|---|
| 53 | Integration Framework Foundation | Codi (be) + Dori | IntegrationProvider, IntegrationConnection, HabitIntegrationMapping, IntegrationActivity models + adapter pattern + GraphQL surface + architecture doc | Large |
| 54 | Strava Integration (backend) | Codi (be) | OAuth flow, webhook receiver, Strava::Adapter, ingestion pipeline, 80% spec coverage | Large |
| 55 | Chess.com Integration (backend) | Codi (be) | Public REST adapter, Crono poller, daily aggregation, 80% spec coverage | Medium |
| 56 | Smart Auto-Mapping Engine | Codi (be) | Integrations::MapIncomingActivity interaction with confidence scoring, Integrations::AutoCheckIn (idempotent wrapper), GoalEvent#source enum + feed-marker glue | Large |
| 57 | Connected Apps Settings UI | Desi → Codi (fe) | UI-SPEC + Vue surface for connect/manage/map; activity feed marker; Storybook stories; Cypress smoke | Large |
| 58 | ToS, Privacy & Release | Dori + Codi (release) | Termly Privacy Policy + ToS updates; user-facing Connected Apps docs; CHANGELOG; feature-flag staged ramp | Medium |
Total estimated AI execution time: 6–10 weeks (matching Penny's estimate for the epic once greenlit).
13. Test Strategy
Backend (RSpec + SimpleCov ≥80%)
- Model validations/associations + uniqueness constraints
- Interaction success + failure paths + side effects (job enqueuing)
- GraphQL request specs for new queries + mutations
- Webhook signature verification
- Encrypted-attribute round-trip test
- OAuth code → token exchange
- Idempotency (same activity can't double-check)
Integration Tests (VCR cassettes)
- Strava OAuth flow (with sandbox credentials)
- Chess.com player profile + games-archive fetch
- Webhook payload parsing + verification
Frontend (Vitest ≥80%)
- Connected Apps list component (status badges, disconnect button)
- Mapping review modal (confidence scores, accept/edit/dismiss)
- Activity feed marker rendering (pill styling, attribution)
- OAuth flow initiation (Capacitor browser open)
E2E / Smoke (Cypress)
- Connect mock Strava provider, verify Connected Apps surface lists it
- Disconnect, verify removal
- (Real OAuth handoff stubbed via
cy.intercept())
Manual QA (Pre-release)
- Phase 57 mobile build with sandboxed Strava app
- Known Chess.com username test account
- Activity feed impression on mobile (pill rendering)
14. Dependencies & Ordering
- Phase 53 must complete before 54, 55, 56 (foundation models + adapter pattern)
- Phase 54 & 55 are parallelizable once 53 lands (independent adapters)
- Phase 56 depends on 54 & 55 (MapIncomingActivity needs both adapters implemented)
- Phase 57 depends on 53, 54, 55, 56 (UI surfaces framework outputs)
- Phase 58 depends on 57 (needs working UI to document)
Suggested dispatch order: 53 (foundation) → 54 + 55 (parallel) → 56 → 57 → 58
15. Implementation Notes (For Codi During Phases 53–58)
Idempotency Strategy
Every imported activity must be idempotent to prevent double-checks. Use a composite key:
IntegrationActivity.find_or_create_by!(
integration_connection_id: connection.id,
provider_activity_id: strava_activity[:id], # Strava's internal activity ID
completed_date: activity_date
)If the same Strava activity arrives twice (webhook retry, re-poll), the second find_or_create_by! returns the existing record — no second check-in.
Token Refresh
Strava tokens expire. On 401 response, refresh via Strava's refresh_token:
def refresh_access_token(connection)
response = Strava::Client.post('/oauth/token', body: {
client_id: ENV['STRAVA_CLIENT_ID'],
client_secret: ENV['STRAVA_CLIENT_SECRET'],
grant_type: 'refresh_token',
refresh_token: connection.refresh_token
})
connection.update!(
access_token: response[:access_token],
refresh_token: response[:refresh_token],
token_expires_at: response[:expires_at]
)
endError Handling & Graceful Degradation
If a Strava webhook fails or a Chess.com poll times out, never queue a check-in. Instead:
- Log the error to Sentry
- Set
connection.status = 'error' - Emit a Sentry alert
- User sees "Connection paused. [Reconnect]" in settings
They must manually reconnect (re-do OAuth) to resume.
Staging & Production Credential Separation
# config/credentials/staging.yml.enc
strava:
client_id: "staging-app-id"
redirect_uri: "https://staging.objectuve.app/oauth/strava/callback"
# config/credentials/production.yml.enc
strava:
client_id: "prod-app-id"
redirect_uri: "https://objectuve.app/oauth/strava/callback"Access in code: ENV['STRAVA_CLIENT_ID'] or Rails.application.credentials.strava.client_id
16. Rollback Strategy
All phases land behind a activity_nexus_enabled feature flag (reuses Feature Switches infra from item #37, already on roadmap). If needed:
- Disable flag → No Connected Apps UI rendered, no webhooks processed (early-exit guard), no polling jobs run.
- Existing manual check-ins unaffected (they use the standard
GoalTracking::CheckInHabitinteraction). - Rollback time: <5 minutes (flag flip via admin dashboard).
17. Suggested Routing (Post-Greenlight)
→ Orion to plan as Phase 53–58 milestone on the roadmap (epic, 6 phases).
→ Josh to confirm version number (originally proposed v1.12; align with current in-flight versions before committing).
→ Maggie to dispatch Phase 53 task package to Codi (foundation models first, specs/docs parallelizable).
→ Riley to bundle all 6 phases into one PR per phase on the milestone branch (gsd/v1.12-activity-nexus or equivalent) — phases merge sequentially as Vicki ships.
18. References & Evidence
- Feasibility research: OBJ-44 comment by Penny (
06b5aa97-e450-4469-822c-835d722d062c) — API landscape analysis, Duolingo ToS findings, Fitbit deprecation. - Epic plan: OBJ-44 comment by Orion (
e2c17c7a-1000-4f32-8c50-4301a840b33f) — task breakdown, assumptions, risks, test strategy. - Josh's greenlight: OBJ-44 comment by Josh (
772dc543-6ac9-4e91-b9ad-c26b5b935ff3) — Phase 6 timing, Chess.com substitute, smart auto-mapping, Strava ToS understood. - Strava API docs: https://developers.strava.com/docs/reference/
- Chess.com API docs: https://www.chess.com/news/view/chess-com-api
- Rails 7 ActiveRecord::Encryption: https://guides.rubyonrails.org/active_record_encryption.html
- Capacitor Browser: https://capacitorjs.com/docs/apis/browser
- MISSION.md values #3 ("Progress is Earned") and #5 ("Accountability, Not Entertainment") — anti-addictive alignment.
Appendix: FAQ
Q: Why not use Duolingo? A: No official public API. Community reverse-engineered packages violate ToS and are fragile. Chess.com is the safe substitute — fully public, no auth, strong market fit (chess learners overlap with habit-builders).
Q: Why is Fitbit not supported? A: Fitbit is deprecated September 2026. Google Health is the official replacement. It's on the roadmap for Phase 2, after MVP validates the integration framework.
Q: What if a user breaks an Objectuve habit and the integration tries to check it? A: AutoCheckIn validates goal exists. If goal is deleted, it fails gracefully (warning log, Sentry alert) and does NOT create a dangling event. This is tested in Phase 54 (Strava) and Phase 55 (Chess.com) spec suites.
Q: Can I export my data (Strava runs) from Objectuve later? A: This PRD does not include data export. One-way import only. Export is a separate feature proposal if needed post-launch.
Q: What about Apple Watch or Garmin devices? A: Apple Health is planned for Phase 2 (leverages Capacitor's existing HealthKit plugin). Garmin requires a business partnership; we'll apply if integration adoption is strong. See Phase 2+ roadmap.
Q: How long are activity records kept? A: IntegrationActivity records follow the same retention as GoalEvent records. Soft-delete (paranoia) applies. See docs/operations/data-retention.md for specifics (outside this PRD's scope).
Status: Ready for Epic Scoping
This PRD captures Penny's research, Orion's epic plan, and Josh's decisions. When Josh decides to begin Activity Nexus work, Maggie routes this PRD + OBJ-44 to Orion to invoke /gsd-new-milestone and start Phase 53 dispatch.
Last updated: 2026-08-02