mirror of
https://github.com/computerim/impactflow-discovery.git
synced 2026-08-27 06:20:36 +00:00
Complete Phase 1: goals, cookie auth, profile editing
Close the remaining Phase 1 DoD gaps and reconcile the browser flow with the auth layer. Goals (5 -> 7 prompts): - Add near-term (6-12mo) and long-term (3-5yr) goal prompts; collect raw text on the conversation and store AI-articulated goal summaries on the profile. Extractor articulates the person's own stated goals (mirror, not compass) and never fabricates. Alembic 003 adds the four columns. Cookie-based browser sessions (fixes frontend<->auth desync): - OAuth callback now sets httpOnly session cookies and redirects into the app instead of returning JSON. get_current_user gains a cookie fallback (X-API-Key -> Bearer -> cookie). refresh/logout read the refresh cookie and set/clear cookies. New shared auth.js (authedFetch) sends cookies and silently refreshes on 401. Static pages drop the bogus user_id and call the correct /me endpoints. Profile editing (read/edit/affirm): - PATCH /discovery/profile/me edits the prose (Ikigai summaries, overlap narrative, goals); owner-scoped, partial update, 409 when locked. Edit mode in profile.html with Save/Cancel. Also: bump default model to claude-sonnet-4-6, align ports to 8011 (OAuth redirect, CORS), add COOKIE_SECURE/POST_LOGIN_REDIRECT config, and refresh the README to match the shipped behavior. Tests: 33 passing (added cookie-auth, profile-edit, goal-extraction cases; factored a shared app_client fixture into conftest.py). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
# ImpactFlow Self-Discovery Module
|
||||
|
||||
ImpactFlow Self-Discovery is a standalone FastAPI service that turns five
|
||||
short reflection stories into a structured Enneagram + Ikigai profile. It is
|
||||
ImpactFlow Self-Discovery is a standalone FastAPI service that turns seven
|
||||
short reflection responses into a structured Enneagram + Ikigai + goals
|
||||
profile. It is
|
||||
designed to run independently from the main ImpactFlow app on port `8011` and
|
||||
can be integrated into the larger product later.
|
||||
|
||||
@@ -15,24 +16,27 @@ If you need to explain this app in detail, use this mental model:
|
||||
|
||||
1. A user opens `/static/discovery.html`.
|
||||
2. The browser sends them through Google sign-in at `GET /api/auth/login`;
|
||||
`GET /api/auth/callback` mints an access JWT + refresh token and the
|
||||
browser stores the access JWT for subsequent requests.
|
||||
3. With the JWT in the `Authorization: Bearer …` header, the browser starts
|
||||
a discovery conversation with `POST /discovery/start` — the backend
|
||||
derives the `user_id` from the JWT, not from the request body.
|
||||
4. The user answers five open-ended prompts.
|
||||
5. The browser saves all five answers with
|
||||
`GET /api/auth/callback` mints an access + refresh token, sets them as
|
||||
httpOnly cookies, and redirects back into the app. The browser holds no
|
||||
tokens itself — the cookies are sent automatically on later requests.
|
||||
3. The browser starts a discovery conversation with `POST /discovery/start` —
|
||||
the backend derives the `user_id` from the session cookie, not the body.
|
||||
4. The user answers seven open-ended prompts: five discovery prompts plus a
|
||||
near-term (6–12 month) and a long-term (3–5 year) goal prompt.
|
||||
5. The browser saves all answers with
|
||||
`PUT /discovery/{conversation_id}/respond`.
|
||||
6. The browser asks the backend to analyze the saved answers with
|
||||
`POST /discovery/{conversation_id}/complete`.
|
||||
7. The backend calls Anthropic through `DiscoveryExtractor`.
|
||||
8. The extractor asks for JSON containing Enneagram, instinctual variant,
|
||||
Ikigai summaries, confidence flags, and optional extraction notes.
|
||||
Ikigai summaries, articulated short- and long-term goals, confidence flags,
|
||||
and optional extraction notes.
|
||||
9. The backend stores that JSON as a `DiscoveryProfile` row owned by the
|
||||
authenticated user.
|
||||
10. The browser redirects to `/static/profile.html`.
|
||||
11. The profile page loads the newest profile with
|
||||
`GET /discovery/profile/me` and lets the user lock it with
|
||||
11. The profile page loads the newest profile with `GET /discovery/profile/me`.
|
||||
While it is unlocked the user can revise their words with
|
||||
`PATCH /discovery/profile/me`, then lock it with
|
||||
`PUT /discovery/profile/me/confirm`.
|
||||
|
||||
Machine-to-machine callers (e.g. the MCP server) skip the OAuth dance and
|
||||
@@ -42,12 +46,13 @@ valid and admin-only routes work without a real Google sign-in.
|
||||
|
||||
The app is intentionally small: static HTML/CSS for the UI, FastAPI for the
|
||||
API, async SQLAlchemy for persistence, Alembic for migrations, SQLite for local
|
||||
storage, Google OAuth + JWT for browser auth, and Anthropic for the analysis
|
||||
storage, Google OAuth + httpOnly cookie sessions for browser auth, and Anthropic for the analysis
|
||||
step.
|
||||
|
||||
## What The App Does
|
||||
|
||||
The module collects five narrative prompts:
|
||||
The module collects seven narrative prompts — five discovery prompts plus two
|
||||
goal prompts:
|
||||
|
||||
| Stored field | User-facing prompt | Purpose in extraction |
|
||||
| --- | --- | --- |
|
||||
@@ -56,6 +61,8 @@ The module collects five narrative prompts:
|
||||
| `prompt_pull` | The Natural Pull | Helps infer instinctual variant and recurring interests |
|
||||
| `prompt_recognition` | The Recognition Moment | Reveals what the person values being seen for |
|
||||
| `prompt_future` | The Future Pull | Helps infer mission, vocation, and ideal future direction |
|
||||
| `prompt_goals_short` | The Near Horizon | The person's own near-term (6–12 month) goals, articulated back |
|
||||
| `prompt_goals_long` | The Long Horizon | The person's own long-term (3–5 year) goals, articulated back |
|
||||
|
||||
The generated profile includes:
|
||||
|
||||
@@ -71,6 +78,8 @@ The generated profile includes:
|
||||
| `mission_summary` | Ikigai: what the world needs from the user |
|
||||
| `vocation_summary` | Ikigai: what the user can be paid for |
|
||||
| `overlap_narrative` | Plain-language convergence narrative |
|
||||
| `short_term_goals` | The user's own near-term (6–12 month) goals, articulated back |
|
||||
| `long_term_goals` | The user's own long-term (3–5 year) goals, articulated back |
|
||||
| `confidence` | Confidence flags for triad, type, variant, and Ikigai |
|
||||
| `extraction_notes` | Optional ambiguity or caveat from the model |
|
||||
| `locked` | Whether the user has confirmed the profile |
|
||||
@@ -87,7 +96,7 @@ Browser static UI MCP server / other machines
|
||||
profile.html
|
||||
| |
|
||||
| Google OAuth + |
|
||||
| Bearer JWT |
|
||||
| session cookie |
|
||||
v v
|
||||
+--------------------------------------------+
|
||||
| FastAPI app |
|
||||
@@ -126,27 +135,33 @@ Important files:
|
||||
| `app/database.py` | Async database engine, session factory, SQLite directory setup |
|
||||
| `app/services/extractor.py` | Anthropic client wrapper, prompt, JSON parsing, retry logic |
|
||||
| `app/migration_bootstrap.py` | Stamps pre-Alembic SQLite DBs as revision `001` so `alembic upgrade head` succeeds on older local databases |
|
||||
| `app/static/discovery.html` | Browser-based five-prompt flow |
|
||||
| `app/static/profile.html` | Browser-based profile display and confirm action |
|
||||
| `app/static/discovery.html` | Browser-based seven-prompt flow |
|
||||
| `app/static/profile.html` | Browser-based profile display, edit, and confirm actions |
|
||||
| `app/static/auth.js` | Shared `authedFetch` helper: sends session cookies, silently refreshes on `401`, redirects to login |
|
||||
| `app/static/style.css` | Shared UI styling |
|
||||
| `alembic/versions/001_initial.py` | Initial database schema migration |
|
||||
| `alembic/versions/002_add_auth.py` | Adds `users`, `refresh_tokens`, and `activity_log` tables |
|
||||
| `tests/test_extractor.py` | Unit tests for extraction plumbing and retry behavior |
|
||||
| `tests/test_auth.py` | Tests for the dual-auth dependency, JWT minting/decode, admin enforcement, and domain allow-list |
|
||||
| `alembic/versions/003_add_goals.py` | Adds the goal columns to `discovery_conversation` and `discovery_profile` |
|
||||
| `tests/conftest.py` | Shared `app_client` fixture (isolated app + temp DB) |
|
||||
| `tests/test_extractor.py` | Unit tests for extraction plumbing, goals, and retry behavior |
|
||||
| `tests/test_auth.py` | Tests for the dual-auth dependency (JWT + cookie + API key), token refresh/logout, admin enforcement, and domain allow-list |
|
||||
| `tests/test_profile_edit.py` | Tests for `PATCH /discovery/profile/me` (edit, partial update, lock/`409`) |
|
||||
| `tests/test_migration_bootstrap.py` | Unit tests for the pre-Alembic SQLite stamping helper |
|
||||
| `tests/test_static_discovery.py` | Guard test for the insecure-context UUID fallback in `discovery.html` |
|
||||
| `tests/fixtures/{gut,head,heart}_type_responses.json` | Synthetic five-prompt responses used by extractor tests |
|
||||
| `tests/test_static_discovery.py` | Guard tests for the static pages' cookie-session and edit contract |
|
||||
| `tests/fixtures/{gut,head,heart}_type_responses.json` | Synthetic seven-prompt responses used by extractor tests |
|
||||
| `smoke_test.py` | In-process end-to-end API smoke test with fake extraction; also exercises the auth and activity tracking surface |
|
||||
|
||||
## Authentication
|
||||
|
||||
The API supports two ways to authenticate, both resolved by a single
|
||||
`get_current_user` dependency in `app/auth.py`:
|
||||
The `get_current_user` dependency in `app/auth.py` resolves a request from
|
||||
three credential sources, tried in order: `X-API-Key`, then an
|
||||
`Authorization: Bearer` access token, then the session cookie.
|
||||
|
||||
| Caller | Mechanism | Notes |
|
||||
| --- | --- | --- |
|
||||
| Browser users | Google OAuth + signed JWT in `Authorization: Bearer …` | Issued by `/api/auth/callback` after a successful Google sign-in |
|
||||
| Machine-to-machine (MCP server, scripts) | `X-API-Key: $IMPACTFLOW_API_KEY` | Resolves to a synthetic admin user `api-key-admin` so FK constraints stay valid |
|
||||
| Browser users | Google OAuth → httpOnly session cookies | Set by `/api/auth/callback`; the access cookie is refreshed silently via `/api/auth/refresh` |
|
||||
| Scripts / API clients | `Authorization: Bearer <access token>` | The same access token presented manually instead of via cookie |
|
||||
| Machine-to-machine (MCP server) | `X-API-Key: $IMPACTFLOW_API_KEY` | Resolves to a synthetic admin user `api-key-admin` so FK constraints stay valid |
|
||||
|
||||
The first real Google user to sign in is auto-promoted to `role=admin`;
|
||||
every subsequent user defaults to `role=user`. Admin-only routes (e.g.
|
||||
@@ -157,8 +172,8 @@ every subsequent user defaults to `role=user`. Admin-only routes (e.g.
|
||||
1. In <https://console.cloud.google.com> open APIs & Services → Credentials.
|
||||
2. Create an **OAuth 2.0 Client ID** of type **Web application**.
|
||||
3. Add authorized redirect URIs that match `OAUTH_REDIRECT_URI` in `.env`:
|
||||
- `http://localhost:8000/api/auth/callback` for local dev
|
||||
- `http://<deploy-host>:8000/api/auth/callback` for the deployed instance
|
||||
- `http://localhost:8011/api/auth/callback` for local dev
|
||||
- `http://<deploy-host>:8011/api/auth/callback` for the deployed instance
|
||||
4. Copy the client id and client secret into `.env` as `GOOGLE_CLIENT_ID` and
|
||||
`GOOGLE_CLIENT_SECRET`.
|
||||
5. Generate two random 64-character strings and put them in `.env` as
|
||||
@@ -240,8 +255,9 @@ step above is what populates the persistent database.
|
||||
|
||||
### 2. Starting A Conversation
|
||||
|
||||
`POST /discovery/start` requires authentication (Bearer JWT or `X-API-Key`).
|
||||
It takes no body — the `user_id` is derived from the authenticated user.
|
||||
`POST /discovery/start` requires authentication (session cookie, Bearer token,
|
||||
or `X-API-Key`). It takes no body — the `user_id` is derived from the
|
||||
authenticated user.
|
||||
|
||||
It creates a `DiscoveryConversation` row with:
|
||||
|
||||
@@ -258,12 +274,13 @@ It returns:
|
||||
}
|
||||
```
|
||||
|
||||
The static UI starts this conversation after the browser has a JWT from the
|
||||
OAuth callback, and retries on submit if the first start call failed.
|
||||
The static UI starts this conversation once the browser has a session cookie
|
||||
from the OAuth callback, and retries on submit if the first start call failed.
|
||||
|
||||
### 3. Saving Answers
|
||||
|
||||
`PUT /discovery/{conversation_id}/respond` accepts all five prompt responses:
|
||||
`PUT /discovery/{conversation_id}/respond` accepts all seven prompt responses
|
||||
(five discovery prompts plus the two goal prompts):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -271,10 +288,14 @@ OAuth callback, and retries on submit if the first start call failed.
|
||||
"prompt_friction": "Something felt wrong when...",
|
||||
"prompt_pull": "I naturally keep returning to...",
|
||||
"prompt_recognition": "I felt seen when...",
|
||||
"prompt_future": "If I could not fail..."
|
||||
"prompt_future": "If I could not fail...",
|
||||
"prompt_goals_short": "In the next 6–12 months I want to...",
|
||||
"prompt_goals_long": "In the next 3–5 years I want to..."
|
||||
}
|
||||
```
|
||||
|
||||
All fields default to empty, so a partial save is accepted.
|
||||
|
||||
It stores the responses on the existing conversation and returns:
|
||||
|
||||
```json
|
||||
@@ -290,12 +311,13 @@ If the conversation id does not exist, it returns `404`.
|
||||
|
||||
`POST /discovery/{conversation_id}/complete` loads the conversation, builds a
|
||||
compact response dictionary with keys `alive`, `friction`, `pull`,
|
||||
`recognition`, and `future`, then calls `DiscoveryExtractor.extract()`.
|
||||
`recognition`, `future`, `goals_short`, and `goals_long`, then calls
|
||||
`DiscoveryExtractor.extract()`.
|
||||
|
||||
The route rejects completion with:
|
||||
|
||||
- `404` if the conversation does not exist
|
||||
- `400` if all five responses are blank
|
||||
- `400` if all seven responses are blank
|
||||
- `502` if Anthropic extraction fails or returns unusable output after retry
|
||||
|
||||
On success, it stores a new `DiscoveryProfile`, marks the conversation
|
||||
@@ -309,28 +331,46 @@ user by descending `generated_at`.
|
||||
The profile page uses this route after redirect. This means one user can have
|
||||
multiple completed conversations, but the UI always displays the latest one.
|
||||
|
||||
### 6. Confirming The Profile
|
||||
### 6. Editing The Profile
|
||||
|
||||
`PATCH /discovery/profile/me` edits the prose of the newest profile. The body
|
||||
is a partial update — only the fields supplied are changed — over the seven
|
||||
editable text fields: `love_summary`, `strength_summary`, `mission_summary`,
|
||||
`vocation_summary`, `overlap_narrative`, `short_term_goals`, and
|
||||
`long_term_goals`. The AI's structural read (`triad`, `probable_type`, `wing`,
|
||||
`instinctual_variant`, `instinctual_stack`) and confidence are not editable
|
||||
here.
|
||||
|
||||
It rejects with:
|
||||
|
||||
- `404` if the user has no profile
|
||||
- `409` if the profile is locked (affirming makes it final)
|
||||
- `400` if the body contains no editable fields
|
||||
|
||||
### 7. Confirming The Profile
|
||||
|
||||
`PUT /discovery/profile/me/confirm` locks the newest profile by setting
|
||||
`locked = true`.
|
||||
|
||||
This is the current confirmation mechanism for "This is me" on the profile
|
||||
page. It does not prevent future conversations from generating newer profiles.
|
||||
`locked = true`. This is the "This is me" affirmation on the profile page;
|
||||
once locked, the profile can no longer be edited (`PATCH` returns `409`). It
|
||||
does not prevent future conversations from generating newer profiles.
|
||||
|
||||
## API Reference
|
||||
|
||||
All `/discovery/*`, `/api/me*`, `/api/activity*`, and `/api/admin/*` routes
|
||||
require authentication (Bearer JWT or `X-API-Key`). `/api/auth/login`,
|
||||
`/api/auth/callback`, `/health`, `/`, and `/static/*` are public.
|
||||
require authentication. The browser is authenticated by the session cookie set
|
||||
at the OAuth callback; machine callers send `X-API-Key`. (A raw
|
||||
`Authorization: Bearer` access token is also still accepted, e.g. for scripted
|
||||
clients.) `/api/auth/login`, `/api/auth/callback`, `/health`, `/`, and
|
||||
`/static/*` are public.
|
||||
|
||||
| Method | Path | Auth | Purpose |
|
||||
| --- | --- | --- | --- |
|
||||
| `GET` | `/` | public | Redirects to `/static/discovery.html` |
|
||||
| `GET` | `/health` | public | Liveness check, returns `{"status": "ok"}` |
|
||||
| `GET` | `/api/auth/login` | public | 302 to Google consent screen |
|
||||
| `GET` | `/api/auth/callback` | public | OAuth callback; issues `{access_token, refresh_token, user}` |
|
||||
| `POST` | `/api/auth/refresh` | public (token in body) | Exchange refresh token for a new access token |
|
||||
| `POST` | `/api/auth/logout` | public (token in body) | Revoke a refresh token |
|
||||
| `GET` | `/api/auth/callback` | public | OAuth callback; sets session cookies and redirects into the app |
|
||||
| `POST` | `/api/auth/refresh` | public (refresh cookie or body) | Mint a new access token; refreshes the access cookie |
|
||||
| `POST` | `/api/auth/logout` | public (refresh cookie or body) | Revoke the refresh token and clear session cookies |
|
||||
| `GET` | `/api/me`, `/api/auth/me` | yes | Current user profile |
|
||||
| `PATCH` | `/api/me` | yes | Update `display_name` |
|
||||
| `GET` | `/api/me/stats` | yes | Per-user usage stats |
|
||||
@@ -340,9 +380,10 @@ require authentication (Bearer JWT or `X-API-Key`). `/api/auth/login`,
|
||||
| `GET` | `/api/activity/summary` | yes | Aggregate activity stats |
|
||||
| `GET` | `/api/admin/activity` | admin | All-users activity feed |
|
||||
| `POST` | `/discovery/start` | yes | Begin a conversation (user derived from auth) |
|
||||
| `PUT` | `/discovery/{conversation_id}/respond` | yes | Save all five responses |
|
||||
| `PUT` | `/discovery/{conversation_id}/respond` | yes | Save all seven responses |
|
||||
| `POST` | `/discovery/{conversation_id}/complete` | yes | Run extraction, store profile, return profile |
|
||||
| `GET` | `/discovery/profile/me` | yes | Fetch newest profile for the authenticated user |
|
||||
| `PATCH` | `/discovery/profile/me` | yes | Edit the newest profile's prose (`409` if locked) |
|
||||
| `PUT` | `/discovery/profile/me/confirm` | yes | Lock newest profile for the authenticated user |
|
||||
| `GET` | `/discovery/conversation/{conversation_id}` | yes | Fetch stored conversation responses (owner only) |
|
||||
|
||||
@@ -395,7 +436,7 @@ Append-only audit trail. Pruned to 90 days on app startup.
|
||||
|
||||
### `discovery_conversation`
|
||||
|
||||
Stores one five-prompt response set.
|
||||
Stores one seven-prompt response set.
|
||||
|
||||
| Column | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
@@ -408,6 +449,8 @@ Stores one five-prompt response set.
|
||||
| `prompt_pull` | text nullable | Third narrative response |
|
||||
| `prompt_recognition` | text nullable | Fourth narrative response |
|
||||
| `prompt_future` | text nullable | Fifth narrative response |
|
||||
| `prompt_goals_short` | text nullable | Near-term (6–12 month) goal response |
|
||||
| `prompt_goals_long` | text nullable | Long-term (3–5 year) goal response |
|
||||
|
||||
### `discovery_profile`
|
||||
|
||||
@@ -429,6 +472,8 @@ Stores one extracted profile for one conversation.
|
||||
| `mission_summary` | text nullable | Ikigai mission summary |
|
||||
| `vocation_summary` | text nullable | Ikigai vocation summary |
|
||||
| `overlap_narrative` | text nullable | Convergence narrative |
|
||||
| `short_term_goals` | text nullable | Articulated near-term (6–12 month) goals |
|
||||
| `long_term_goals` | text nullable | Articulated long-term (3–5 year) goals |
|
||||
| `confidence_json` | text nullable | JSON string for confidence flags |
|
||||
| `locked` | boolean | Defaults to false |
|
||||
|
||||
@@ -438,7 +483,7 @@ Stores one extracted profile for one conversation.
|
||||
logic hidden elsewhere. It:
|
||||
|
||||
1. Validates that `ANTHROPIC_API_KEY` exists.
|
||||
2. Builds one user message from the five responses.
|
||||
2. Builds one user message from the seven responses.
|
||||
3. Calls Anthropic with `SYSTEM_PROMPT`.
|
||||
4. Parses the model response as JSON.
|
||||
5. Strips Markdown code fences if present.
|
||||
@@ -453,7 +498,7 @@ Default extraction settings:
|
||||
|
||||
| Setting | Value |
|
||||
| --- | --- |
|
||||
| Default model | `claude-sonnet-4-5` |
|
||||
| Default model | `claude-sonnet-4-6` |
|
||||
| Max tokens | `2000` |
|
||||
| Required response type | Single JSON object |
|
||||
| Retry count | One retry after invalid JSON or missing keys |
|
||||
@@ -467,30 +512,44 @@ The system prompt instructs the model to infer:
|
||||
|
||||
## Frontend Behavior
|
||||
|
||||
The frontend is static HTML with embedded JavaScript.
|
||||
The frontend is static HTML with embedded JavaScript, served from `/static`.
|
||||
The browser session is a server-set httpOnly cookie (issued by the OAuth
|
||||
callback), so the pages hold no tokens of their own.
|
||||
|
||||
`auth.js` (shared helper):
|
||||
|
||||
- `authedFetch()` sends every request with `credentials: "include"` so the
|
||||
session cookies ride along
|
||||
- on a `401` it makes a single silent `POST /api/auth/refresh` (the refresh
|
||||
cookie is scoped to `/api/auth`) and retries the original request
|
||||
- if the refresh also fails, it redirects to `GET /api/auth/login` — which is
|
||||
also how an unauthenticated first visit gets bounced through Google sign-in
|
||||
|
||||
`discovery.html`:
|
||||
|
||||
- stores a generated `impactflow_user_id` in `localStorage`
|
||||
- generates that id via `createUserId()`, which prefers `crypto.randomUUID()`
|
||||
when available and falls back to `crypto.getRandomValues()` so the flow
|
||||
still works in insecure contexts (e.g. plain `http://` over LAN)
|
||||
- shows five prompts one at a time
|
||||
- shows the seven prompts one at a time (five discovery prompts plus near-term
|
||||
and long-term goal prompts)
|
||||
- keeps answers in memory while navigating back and next
|
||||
- starts a conversation on page load
|
||||
- saves all responses on submit
|
||||
- triggers extraction
|
||||
- redirects to the profile page on success
|
||||
- starts a conversation on page load via `authedFetch("/discovery/start")`
|
||||
- saves all responses on submit (`PUT /discovery/{id}/respond`), then triggers
|
||||
extraction (`POST /discovery/{id}/complete`)
|
||||
- redirects to `/static/profile.html` on success — no user id in the URL, since
|
||||
the backend derives the user from the session cookie
|
||||
- shows an error box and reload button if submission fails
|
||||
|
||||
`profile.html`:
|
||||
|
||||
- reads `user_id` from the query string
|
||||
- fetches the newest profile for that user
|
||||
- fetches the authenticated user's newest profile via `GET /discovery/profile/me`
|
||||
- escapes all model-generated text before rendering
|
||||
- shows Ikigai cards and a triad description
|
||||
- uses confidence dots for triad and Ikigai confidence
|
||||
- sends the confirm request when the user clicks "This is me"
|
||||
- shows the overlap narrative, Ikigai cards, a triad description, and the
|
||||
person's near-term/long-term goals, with confidence dots for triad and Ikigai
|
||||
- while the profile is unlocked, offers two actions: "Edit my words" and
|
||||
"This is me"
|
||||
- edit mode turns the narrative, the four Ikigai summaries, and both goal
|
||||
fields into textareas and saves with `PATCH /discovery/profile/me`; the AI's
|
||||
structural read (triad/type/wing/variant) is shown but not editable here
|
||||
- "This is me" locks the profile with `PUT /discovery/profile/me/confirm`; a
|
||||
locked profile shows the confirmed state and no longer offers edit
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -513,15 +572,17 @@ Environment variables:
|
||||
| `DATABASE_URL` | `sqlite+aiosqlite:///./data/discovery.db` | Async SQLAlchemy database URL |
|
||||
| `HOST_BIND_IP` | `0.0.0.0` | Docker host IP for publishing port `8011`; use this when Docker runs inside WSL |
|
||||
| `HOST_PORT` | `8011` | WSL/Docker host port; Windows portproxy exposes the same port |
|
||||
| `ANTHROPIC_MODEL` | `claude-sonnet-4-5` | Override extraction model |
|
||||
| `ANTHROPIC_MODEL` | `claude-sonnet-4-6` | Override extraction model |
|
||||
| `GOOGLE_CLIENT_ID` | required for browser auth | OAuth 2.0 client id from Google Cloud Console |
|
||||
| `GOOGLE_CLIENT_SECRET` | required for browser auth | OAuth 2.0 client secret |
|
||||
| `OAUTH_REDIRECT_URI` | derived from request | Override the callback URL Google redirects to; must be registered in the Cloud Console |
|
||||
| `JWT_SECRET` | required | HS256 signing key for access tokens; also used for the OAuth `state` session cookie |
|
||||
| `JWT_ACCESS_MINUTES` | `15` | Access-token lifetime |
|
||||
| `JWT_REFRESH_DAYS` | `7` | Refresh-token lifetime |
|
||||
| `COOKIE_SECURE` | `true` | Set `false` for local `http://localhost` dev (Secure cookies aren't sent over plain http); must be `true` in production over HTTPS |
|
||||
| `POST_LOGIN_REDIRECT` | `/static/discovery.html` | Path the OAuth callback redirects to after setting the session cookies |
|
||||
| `IMPACTFLOW_API_KEY` | required for MCP/machine auth | Header value for `X-API-Key`; resolves to the synthetic admin user |
|
||||
| `CORS_ALLOWED_ORIGINS` | `http://localhost:8000` | Comma-separated allow-list of browser origins |
|
||||
| `CORS_ALLOWED_ORIGINS` | `http://localhost:8011` | Comma-separated allow-list of browser origins |
|
||||
| `ALLOWED_EMAIL_DOMAINS` | empty (any) | Comma-separated allow-list of email domains; empty means accept any verified Google email |
|
||||
|
||||
`app/database.py` creates the SQLite directory automatically when the URL uses
|
||||
@@ -648,20 +709,19 @@ reload, except for the browser's normal form restoration behavior.
|
||||
## Integration Notes
|
||||
|
||||
This service is ready to be called from a larger ImpactFlow app. The
|
||||
authenticated user identity now comes from Google OAuth on the browser side
|
||||
and `X-API-Key` on the machine-to-machine side; the per-call `user_id` body
|
||||
parameter is gone.
|
||||
authenticated user identity comes from Google OAuth (httpOnly session cookies)
|
||||
on the browser side and `X-API-Key` on the machine-to-machine side; the
|
||||
per-call `user_id` body parameter is gone.
|
||||
|
||||
Likely integration points:
|
||||
|
||||
- route users into `/static/discovery.html` (or recreate the flow in the main
|
||||
UI) and rely on the JWT issued by `/api/auth/callback` for subsequent calls
|
||||
- the static frontend still needs to be updated to consume the new auth flow
|
||||
(read the JWT from the callback response, store it, and send it as a
|
||||
`Bearer` header on every `/discovery/*` call)
|
||||
UI); the session cookie set by `/api/auth/callback` is sent automatically on
|
||||
subsequent calls, and `auth.js` handles silent token refresh on `401`
|
||||
- the MCP server should send `X-API-Key: $IMPACTFLOW_API_KEY` on every
|
||||
request — no OAuth dance needed
|
||||
- use `locked` as the user's confirmation signal
|
||||
- use `locked` as the user's confirmation signal, and `PATCH
|
||||
/discovery/profile/me` if the surrounding app wants its own edit affordance
|
||||
- decide whether future profiles should supersede locked profiles or be
|
||||
versioned in the main product experience
|
||||
|
||||
|
||||
Reference in New Issue
Block a user