- Enable backend tests in CI (remove if: false) - Fix test_products_helpers.py to pass current_user parameter - Fix test_routines_helpers.py to include short_id in products - Fix llm_context.py to use product_effect_profile correctly - All 221 tests passing
604 lines
49 KiB
Markdown
604 lines
49 KiB
Markdown
# Multi-User Support with Authelia OIDC
|
|
|
|
## TL;DR
|
|
> **Summary**: Convert the monorepo from a single-user personal system into a multi-user application authenticated by Authelia OIDC, with SvelteKit owning the login/session flow and FastAPI enforcing row-level ownership and household-scoped inventory sharing.
|
|
> **Deliverables**:
|
|
> - OIDC login/logout/session flow in SvelteKit
|
|
> - FastAPI token validation, current-user resolution, and authorization helpers
|
|
> - New local identity/household schema plus ownership migrations for existing data
|
|
> - Household-shared inventory support with owner/admin product controls
|
|
> - Updated infra, CI, and verification coverage for the new auth model
|
|
> **Effort**: XL
|
|
> **Parallel**: YES - 3 waves
|
|
> **Critical Path**: T1 -> T2 -> T3 -> T4 -> T5/T6 -> T7/T8 -> T11
|
|
|
|
## Context
|
|
### Original Request
|
|
Add multi-user support with login handled by Authelia using OpenID Connect.
|
|
|
|
### Interview Summary
|
|
- Auth model: app-managed OIDC with SvelteKit-owned session handling; FastAPI acts as the resource server.
|
|
- Roles: `admin` and `member`; admins can manage member data and household memberships, but v1 excludes impersonation and a full user-management console.
|
|
- Ownership model: records are user-owned by default; `products` stay user-owned in v1.
|
|
- Sharing exception: product inventory may be shared among members of the same household; shared household members may view and update inventory entries, but only the product owner or an admin may edit/delete the underlying product.
|
|
- Rollout: retrofit the existing application in one implementation plan rather than staging auth separately.
|
|
- Identity source: Authelia remains the source of truth; no in-app signup/provisioning UI in v1.
|
|
- Verification preference: do not add a permanent frontend test suite in this pass; still require backend tests plus agent-executed QA scenarios.
|
|
|
|
### Metis Review (gaps addressed)
|
|
- Made household sharing explicit with a local `households` + `household_memberships` model instead of overloading OIDC groups.
|
|
- Added a deterministic legacy-data backfill step so existing single-user records are assigned to the first configured admin identity during migration.
|
|
- Called out `llm_context.py`, helper functions like `get_or_404()`, and all row-fetching routes as mandatory scoping points so no single-user path survives.
|
|
- Chose JWT access-token validation via Authelia JWKS for FastAPI, with SvelteKit calling `userinfo` to hydrate the app session and local user record.
|
|
- Kept browser QA agent-executed and out of repo while still requiring backend auth tests and CI enablement.
|
|
|
|
## Work Objectives
|
|
### Core Objective
|
|
Implement a secure, decision-complete multi-user architecture that uses Authelia OIDC for authentication, local app users/households for authorization, row ownership across existing data models, and household-scoped inventory sharing without broadening scope into a full account-management product.
|
|
|
|
### Deliverables
|
|
- Backend identity/auth models for local users, households, memberships, and role mapping.
|
|
- Alembic migration/backfill converting all existing domain data to owned records.
|
|
- FastAPI auth dependencies, token validation, and authorization utilities.
|
|
- Retrofitted API routes and LLM context builders that enforce ownership.
|
|
- SvelteKit login, callback, logout, refresh, and protected-route behavior.
|
|
- Auth-aware API access from frontend server actions and protected page loads.
|
|
- Admin-only backend endpoints for household membership management without a UI console.
|
|
- nginx, deploy, CI, and environment updates needed for OIDC rollout.
|
|
|
|
### Definition of Done (verifiable conditions with commands)
|
|
- `cd backend && uv run pytest`
|
|
- `cd backend && uv run ruff check .`
|
|
- `cd frontend && pnpm check`
|
|
- `cd frontend && pnpm lint`
|
|
- `cd frontend && pnpm build`
|
|
- `cd backend && uv run python -c "import json; from main import app; print(json.dumps(app.openapi())[:200])"`
|
|
|
|
### Must Have
|
|
- OIDC Authorization Code flow with PKCE, server-handled callback, HTTP-only app session cookie, refresh-token renewal, and logout.
|
|
- FastAPI bearer-token validation against Authelia JWKS; no trusted identity headers between app tiers.
|
|
- Local `users`, `households`, and `household_memberships` tables keyed by `issuer + sub` rather than email.
|
|
- `user_id` ownership enforcement across profile, health, routines, skincare, AI logs, and products.
|
|
- Household inventory-sharing model that permits view/update of shared inventory by household members while preserving owner/admin control of product records.
|
|
- Deterministic backfill of legacy records to a configured bootstrap admin identity.
|
|
- Admin/member authorization rules enforced in backend dependencies and mirrored in frontend navigation/controls.
|
|
- Backend auth and authorization tests, plus CI job enablement for those tests.
|
|
|
|
### Must NOT Have (guardrails, AI slop patterns, scope boundaries)
|
|
- No proxy-header trust model between SvelteKit and FastAPI.
|
|
- No in-app signup, password reset, email verification, impersonation, or full user-management console.
|
|
- No multi-household membership per user in v1.
|
|
- No global shared product catalog refactor in this pass.
|
|
- No audit-log productization, notification system, or support tooling.
|
|
- No permanent Playwright/Vitest suite added to the repo in this pass.
|
|
|
|
## Verification Strategy
|
|
> ZERO HUMAN INTERVENTION - all verification is agent-executed.
|
|
- Test decision: tests-after using existing backend `pytest` + `TestClient`; no new committed frontend suite, but include agent-executed browser QA and curl-based verification.
|
|
- QA policy: every task includes happy-path and failure/edge-case scenarios with exact commands or browser actions.
|
|
- Evidence: `.sisyphus/evidence/task-{N}-{slug}.{ext}`
|
|
|
|
## Execution Strategy
|
|
### Parallel Execution Waves
|
|
> Target: 5-8 tasks per wave. <3 per wave (except final) = under-splitting.
|
|
> Extract shared dependencies as Wave-1 tasks for max parallelism.
|
|
|
|
Wave 1: T1 identity models, T2 ownership migration, T3 backend token validation, T4 tenant-aware authorization helpers
|
|
|
|
Wave 2: T5 product/inventory authorization retrofit, T6 remaining domain scoping retrofit, T7 SvelteKit auth/session flow, T8 frontend auth-aware plumbing and shell behavior
|
|
|
|
Wave 3: T9 admin household-management endpoints, T10 infra/env/CI/deploy updates, T11 backend auth regression coverage and release verification
|
|
|
|
### Dependency Matrix (full, all tasks)
|
|
| Task | Depends On | Blocks |
|
|
| --- | --- | --- |
|
|
| T1 | - | T2, T3, T4, T9 |
|
|
| T2 | T1 | T5, T6, T11 |
|
|
| T3 | T1 | T4, T5, T6, T7, T8, T9, T11 |
|
|
| T4 | T1, T3 | T5, T6, T9 |
|
|
| T5 | T2, T3, T4 | T11 |
|
|
| T6 | T2, T3, T4 | T11 |
|
|
| T7 | T3 | T8, T10, T11 |
|
|
| T8 | T7 | T11 |
|
|
| T9 | T1, T2, T3, T4 | T11 |
|
|
| T10 | T3, T7 | T11 |
|
|
| T11 | T2, T3, T4, T5, T6, T7, T8, T9, T10 | Final verification |
|
|
|
|
### Agent Dispatch Summary (wave -> task count -> categories)
|
|
- Wave 1 -> 4 tasks -> `deep`, `unspecified-high`
|
|
- Wave 2 -> 4 tasks -> `deep`, `unspecified-high`, `writing`
|
|
- Wave 3 -> 3 tasks -> `unspecified-high`, `writing`, `deep`
|
|
|
|
## TODOs
|
|
> Implementation + Test = ONE task. Never separate.
|
|
> EVERY task MUST have: Agent Profile + Parallelization + QA Scenarios.
|
|
|
|
- [x] T1. Add local identity, role, household, and sharing models
|
|
|
|
**What to do**: Add a new backend model module for `User`, `Household`, and `HouseholdMembership`; extend existing domain models with ownership fields; add a compact role enum (`admin`, `member`) and a household-membership role enum (`owner`, `member`). Use `issuer + subject` as the immutable OIDC identity key, enforce at most one household membership per user in v1, and add `is_household_shared: bool = False` to `ProductInventory` so sharing is opt-in per inventory row rather than automatic for an entire household.
|
|
**Must NOT do**: Do not key users by email, do not introduce multi-household membership, do not split `Product` into catalog vs overlay tables in this pass, and do not add frontend management UI here.
|
|
|
|
**Recommended Agent Profile**:
|
|
- Category: `deep` - Reason: cross-cutting schema design with downstream auth and authorization consequences
|
|
- Skills: `[]` - Existing backend conventions are the main source of truth
|
|
- Omitted: `svelte-code-writer` - No Svelte files belong in this task
|
|
|
|
**Parallelization**: Can Parallel: NO | Wave 1 | Blocks: T2, T3, T4, T9 | Blocked By: -
|
|
|
|
**References** (executor has NO interview context - be exhaustive):
|
|
- Pattern: `backend/innercontext/models/profile.py:13` - Simple SQLModel table with UUID PK and timestamp conventions to follow for user-owned profile data.
|
|
- Pattern: `backend/innercontext/models/product.py:138` - Main table-model style, JSON-column usage, and `updated_at` pattern.
|
|
- Pattern: `backend/innercontext/models/product.py:353` - Existing `ProductInventory` table to extend with ownership and sharing fields.
|
|
- Pattern: `backend/innercontext/models/__init__.py:1` - Export surface that must include every new model/type.
|
|
- API/Type: `backend/innercontext/models/enums.py` - Existing enum location; add role enums here unless a dedicated auth model module makes more sense.
|
|
|
|
**Acceptance Criteria** (agent-executable only):
|
|
- [ ] `backend/innercontext/models/` defines `User`, `Household`, and `HouseholdMembership` with UUID PKs, timestamps, uniqueness on `(oidc_issuer, oidc_subject)`, and one-household-per-user enforcement.
|
|
- [ ] `Product`, `ProductInventory`, `UserProfile`, `MedicationEntry`, `MedicationUsage`, `LabResult`, `Routine`, `RoutineStep`, `GroomingSchedule`, `SkinConditionSnapshot`, and `AICallLog` each expose an ownership field (`user_id`) in model code, with `ProductInventory` also exposing `is_household_shared`.
|
|
- [ ] `innercontext.models` re-exports the new auth/household types so metadata loading and imports continue to work.
|
|
- [ ] `cd backend && uv run python -c "import innercontext.models as m; print(all(hasattr(m, name) for name in ['User','Household','HouseholdMembership']))"` prints `True`.
|
|
|
|
**QA Scenarios** (MANDATORY - task incomplete without these):
|
|
```
|
|
Scenario: Identity models load into SQLModel metadata
|
|
Tool: Bash
|
|
Steps: Run `cd backend && uv run python -c "import innercontext.models; from sqlmodel import SQLModel; print(sorted(t.name for t in SQLModel.metadata.sorted_tables if t.name in {'users','households','household_memberships'}))" > ../.sisyphus/evidence/task-T1-identity-models.txt`
|
|
Expected: Evidence file lists `['household_memberships', 'households', 'users']`
|
|
Evidence: .sisyphus/evidence/task-T1-identity-models.txt
|
|
|
|
Scenario: Product inventory sharing stays opt-in
|
|
Tool: Bash
|
|
Steps: Run `cd backend && uv run python -c "from innercontext.models.product import ProductInventory; f=ProductInventory.model_fields['is_household_shared']; print(f.default)" > ../.sisyphus/evidence/task-T1-sharing-default.txt`
|
|
Expected: Evidence file contains `False`
|
|
Evidence: .sisyphus/evidence/task-T1-sharing-default.txt
|
|
```
|
|
|
|
**Commit**: YES | Message: `feat(auth): add local user and household models` | Files: `backend/innercontext/models/*`
|
|
|
|
- [x] T2. Add Alembic migration and bootstrap backfill for legacy single-user data
|
|
|
|
**What to do**: Create an Alembic revision that creates `users`, `households`, and `household_memberships`, adds `user_id` ownership columns and related foreign keys/indexes to all owned tables, and adds `is_household_shared` to `product_inventory`. Use a two-step migration: add nullable columns, create/bootstrap a local admin user + default household from environment variables, backfill every existing row to that bootstrap user, then enforce non-null ownership constraints. Use env names `BOOTSTRAP_ADMIN_OIDC_ISSUER`, `BOOTSTRAP_ADMIN_OIDC_SUB`, `BOOTSTRAP_ADMIN_EMAIL`, `BOOTSTRAP_ADMIN_NAME`, and `BOOTSTRAP_HOUSEHOLD_NAME`; abort the migration with a clear error if legacy data exists and the required issuer/sub values are missing.
|
|
**Must NOT do**: Do not assign ownership based on email matching, do not silently create random bootstrap identities, and do not leave owned tables nullable after the migration completes.
|
|
|
|
**Recommended Agent Profile**:
|
|
- Category: `deep` - Reason: schema migration, backfill, and irreversible data-shape change
|
|
- Skills: `[]` - Use existing Alembic patterns from the repo
|
|
- Omitted: `git-master` - Commit strategy is already prescribed here
|
|
|
|
**Parallelization**: Can Parallel: NO | Wave 1 | Blocks: T5, T6, T11 | Blocked By: T1
|
|
|
|
**References** (executor has NO interview context - be exhaustive):
|
|
- Pattern: `backend/alembic/versions/` - Existing migration naming/layout conventions to follow.
|
|
- Pattern: `backend/innercontext/models/product.py:180` - Timestamp/nullability expectations that migrated columns must preserve.
|
|
- Pattern: `backend/db.py:17` - Metadata creation path; migration must leave runtime startup compatible.
|
|
- API/Type: `backend/innercontext/models/profile.py:13` - Existing singleton-style table that must become owned data.
|
|
- API/Type: `backend/innercontext/models/product.py:353` - Inventory table receiving the sharing flag.
|
|
|
|
**Acceptance Criteria** (agent-executable only):
|
|
- [ ] A new Alembic revision exists under `backend/alembic/versions/` creating auth/household tables and ownership columns/indexes/foreign keys.
|
|
- [ ] The migration backfills all existing owned rows to the bootstrap admin user and creates that user's default household + owner membership.
|
|
- [ ] The migration aborts with a readable exception if legacy data exists and `BOOTSTRAP_ADMIN_OIDC_ISSUER` or `BOOTSTRAP_ADMIN_OIDC_SUB` is absent.
|
|
- [ ] Owned tables end with non-null `user_id` constraints after upgrade.
|
|
|
|
**QA Scenarios** (MANDATORY - task incomplete without these):
|
|
```
|
|
Scenario: Migration upgrade succeeds with bootstrap identity configured
|
|
Tool: Bash
|
|
Steps: Create a disposable DB URL (for example `sqlite:///../.sisyphus/evidence/task-T2-upgrade.sqlite`), then run `cd backend && DATABASE_URL=sqlite:///../.sisyphus/evidence/task-T2-upgrade.sqlite BOOTSTRAP_ADMIN_OIDC_ISSUER=https://auth.example.test BOOTSTRAP_ADMIN_OIDC_SUB=legacy-admin BOOTSTRAP_ADMIN_EMAIL=owner@example.test BOOTSTRAP_ADMIN_NAME='Legacy Owner' BOOTSTRAP_HOUSEHOLD_NAME='Default Household' uv run alembic upgrade head > ../.sisyphus/evidence/task-T2-migration-upgrade.txt`
|
|
Expected: Command exits 0 and evidence file shows Alembic reached `head`
|
|
Evidence: .sisyphus/evidence/task-T2-migration-upgrade.txt
|
|
|
|
Scenario: Migration fails fast when bootstrap identity is missing for legacy data
|
|
Tool: Bash
|
|
Steps: Seed a disposable SQLite DB with one legacy row using the pre-migration schema, then run `cd backend && DATABASE_URL=sqlite:///../.sisyphus/evidence/task-T2-missing-bootstrap.sqlite uv run alembic upgrade head 2> ../.sisyphus/evidence/task-T2-migration-missing-bootstrap.txt`
|
|
Expected: Upgrade exits non-zero and evidence contains a message naming both missing bootstrap env vars
|
|
Evidence: .sisyphus/evidence/task-T2-migration-missing-bootstrap.txt
|
|
```
|
|
|
|
**Commit**: YES | Message: `feat(db): backfill tenant ownership for existing records` | Files: `backend/alembic/versions/*`, `backend/innercontext/models/*`
|
|
|
|
- [x] T3. Implement FastAPI token validation, user sync, and current-user dependencies
|
|
|
|
**What to do**: Add backend auth modules that validate Authelia JWT access tokens via JWKS with cached key material, enforce issuer/audience/expiry checks, map role groups to local roles, and expose dependencies like `get_current_user()` and `require_admin()`. Create protected auth endpoints for session sync and self introspection (for example `/auth/session/sync` and `/auth/me`) so SvelteKit can exchange token-derived/userinfo-derived identity details for a local `User` row and current app profile. Use env/config values for issuer, JWKS URL/discovery URL, client ID, and group names instead of hard-coding them.
|
|
**Must NOT do**: Do not trust `X-Forwarded-User`-style headers, do not skip signature validation, do not derive role from email domain, and do not make backend routes public except health-check.
|
|
|
|
**Recommended Agent Profile**:
|
|
- Category: `unspecified-high` - Reason: focused backend auth implementation with security-sensitive logic
|
|
- Skills: `[]` - No project skill is better than direct backend work here
|
|
- Omitted: `svelte-code-writer` - No Svelte components involved
|
|
|
|
**Parallelization**: Can Parallel: NO | Wave 1 | Blocks: T4, T5, T6, T7, T8, T9, T11 | Blocked By: T1
|
|
|
|
**References** (executor has NO interview context - be exhaustive):
|
|
- Pattern: `backend/main.py:37` - Current FastAPI app construction and router registration point.
|
|
- Pattern: `backend/db.py:12` - Session dependency shape that auth dependencies must compose with.
|
|
- Pattern: `backend/innercontext/api/profile.py:27` - Router/dependency style used throughout the API.
|
|
- External: `https://www.authelia.com/configuration/identity-providers/openid-connect/provider/` - OIDC provider/discovery and JWKS behavior.
|
|
- External: `https://www.authelia.com/integration/openid-connect/openid-connect-1.0-claims/` - Claims and userinfo behavior; use `issuer + sub` as identity key.
|
|
|
|
**Acceptance Criteria** (agent-executable only):
|
|
- [ ] A backend auth module validates bearer tokens against Authelia JWKS with issuer/audience checks and cached key refresh.
|
|
- [ ] Protected dependencies expose a normalized current user object with local `user_id`, role, and household membership information.
|
|
- [ ] Backend includes protected auth sync/introspection endpoints used by SvelteKit to upsert local users from OIDC identity data.
|
|
- [ ] Unauthenticated access to owned API routes returns `401`; authenticated access with a valid token reaches router logic.
|
|
|
|
**QA Scenarios** (MANDATORY - task incomplete without these):
|
|
```
|
|
Scenario: Valid bearer token resolves a current user
|
|
Tool: Bash
|
|
Steps: Run `cd backend && uv run pytest tests/test_auth.py -k sync > ../.sisyphus/evidence/task-T3-auth-sync.txt`
|
|
Expected: Auth sync/introspection tests pass and evidence includes the protected auth endpoint names
|
|
Evidence: .sisyphus/evidence/task-T3-auth-sync.txt
|
|
|
|
Scenario: Missing or invalid bearer token is rejected
|
|
Tool: Bash
|
|
Steps: Run `cd backend && uv run pytest tests/test_auth.py -k unauthorized > ../.sisyphus/evidence/task-T3-auth-unauthorized.txt`
|
|
Expected: Tests pass and evidence shows `401` expectations
|
|
Evidence: .sisyphus/evidence/task-T3-auth-unauthorized.txt
|
|
```
|
|
|
|
**Commit**: YES | Message: `feat(auth): validate Authelia tokens in FastAPI` | Files: `backend/main.py`, `backend/innercontext/auth.py`, `backend/innercontext/api/auth*.py`
|
|
|
|
- [x] T4. Centralize tenant-aware fetch helpers and authorization predicates
|
|
|
|
**What to do**: Replace single-user helper assumptions with reusable authorization helpers that every router can call. Add tenant-aware helpers for owned lookup, admin override, same-household checks, and household-shared inventory visibility/update rules. Keep `get_session()` unchanged, but add helpers/dependencies that make it difficult for routers to accidentally query global rows. Update or supersede `get_or_404()` with helpers that scope by `user_id` and return `404` for unauthorized record lookups unless the route intentionally needs `403`.
|
|
**Must NOT do**: Do not leave routers performing raw `session.get()` on owned models, do not duplicate household-sharing logic in every route, and do not use admin bypasses that skip existence checks.
|
|
|
|
**Recommended Agent Profile**:
|
|
- Category: `deep` - Reason: authorization rules must become the shared execution path for many routers
|
|
- Skills: `[]` - This is backend architecture work, not skill-driven tooling
|
|
- Omitted: `frontend-design` - No UI work belongs here
|
|
|
|
**Parallelization**: Can Parallel: NO | Wave 1 | Blocks: T5, T6, T9 | Blocked By: T1, T3
|
|
|
|
**References** (executor has NO interview context - be exhaustive):
|
|
- Pattern: `backend/innercontext/api/utils.py:9` - Existing naive `get_or_404()` helper that must no longer be used for owned records.
|
|
- Pattern: `backend/innercontext/api/products.py:934` - Current direct object fetch/update/delete route pattern to replace.
|
|
- Pattern: `backend/innercontext/api/inventory.py:14` - Inventory routes that currently expose rows globally.
|
|
- Pattern: `backend/innercontext/api/health.py:141` - Representative list/get/update/delete health routes requiring shared helpers.
|
|
- Pattern: `backend/innercontext/api/routines.py:674` - Another high-volume router that must consume the same authz utilities.
|
|
|
|
**Acceptance Criteria** (agent-executable only):
|
|
- [ ] Backend provides shared helper/dependency functions for owned lookups, admin checks, same-household checks, and shared-inventory updates.
|
|
- [ ] `get_or_404()` is either retired for owned data or wrapped so no owned router path still uses the unscoped helper directly.
|
|
- [ ] Shared inventory authorization distinguishes product ownership from inventory update rights.
|
|
- [ ] Helper tests cover owner access, admin override, same-household shared inventory access, and cross-household denial.
|
|
|
|
**QA Scenarios** (MANDATORY - task incomplete without these):
|
|
```
|
|
Scenario: Authorization helpers allow owner/admin/household-shared access correctly
|
|
Tool: Bash
|
|
Steps: Run `cd backend && uv run pytest tests/test_authz.py -k 'owner or admin or household' > ../.sisyphus/evidence/task-T4-authz-happy.txt`
|
|
Expected: Tests pass and evidence includes owner/admin/household cases
|
|
Evidence: .sisyphus/evidence/task-T4-authz-happy.txt
|
|
|
|
Scenario: Cross-household access is denied without leaking row existence
|
|
Tool: Bash
|
|
Steps: Run `cd backend && uv run pytest tests/test_authz.py -k denied > ../.sisyphus/evidence/task-T4-authz-denied.txt`
|
|
Expected: Tests pass and evidence shows `404` or `403` assertions exactly where specified by the helper contract
|
|
Evidence: .sisyphus/evidence/task-T4-authz-denied.txt
|
|
```
|
|
|
|
**Commit**: YES | Message: `refactor(api): centralize tenant authorization helpers` | Files: `backend/innercontext/api/utils.py`, `backend/innercontext/api/authz.py`, router call sites
|
|
|
|
- [x] T5. Retrofit products and inventory endpoints for owned access plus household sharing
|
|
|
|
**What to do**: Update `products` and `inventory` APIs so product visibility is `owned OR household-visible-via-shared-inventory OR admin`, while product mutation remains `owner OR admin`. Keep `Product` user-owned. For household members, allow `GET` on shared products/inventory rows and `PATCH` on shared inventory rows, but keep `POST /products`, `PATCH /products/{id}`, `DELETE /products/{id}`, `POST /products/{id}/inventory`, and `DELETE /inventory/{id}` restricted to owner/admin. Reuse the existing `ProductListItem.is_owned` field so shared-but-not-owned products are clearly marked in summaries. Ensure suggestion and summary endpoints only use products accessible to the current user.
|
|
**Must NOT do**: Do not expose non-shared inventory across a household, do not let household members edit `personal_tolerance_notes`, and do not return global product lists anymore.
|
|
|
|
**Recommended Agent Profile**:
|
|
- Category: `deep` - Reason: most nuanced authorization rules live in product and inventory flows
|
|
- Skills: `[]` - Backend logic and existing product patterns are sufficient
|
|
- Omitted: `frontend-design` - No UI polish belongs here
|
|
|
|
**Parallelization**: Can Parallel: YES | Wave 2 | Blocks: T11 | Blocked By: T2, T3, T4
|
|
|
|
**References** (executor has NO interview context - be exhaustive):
|
|
- Pattern: `backend/innercontext/api/products.py:605` - List route currently returning global products.
|
|
- Pattern: `backend/innercontext/api/products.py:844` - Summary route already exposes `is_owned`; extend rather than replacing it.
|
|
- Pattern: `backend/innercontext/api/products.py:934` - Detail/update/delete routes that currently use direct lookup.
|
|
- Pattern: `backend/innercontext/api/products.py:977` - Product inventory list/create routes.
|
|
- Pattern: `backend/innercontext/api/inventory.py:14` - Direct inventory get/update/delete routes that currently bypass ownership.
|
|
- API/Type: `backend/innercontext/models/product.py:353` - Inventory model fields involved in household sharing.
|
|
- Test: `backend/tests/test_products.py:38` - Existing CRUD/filter test style to extend for authz cases.
|
|
|
|
**Acceptance Criteria** (agent-executable only):
|
|
- [ ] Product list/detail/summary/suggest endpoints only return products accessible to the current user.
|
|
- [ ] Shared household members can `GET` shared products/inventory and `PATCH` shared inventory rows, but cannot mutate product records or create/delete another user's inventory rows.
|
|
- [ ] Product summaries preserve `is_owned` semantics for shared products.
|
|
- [ ] Product/inventory tests cover owner, admin, same-household shared member, and different-household member cases.
|
|
|
|
**QA Scenarios** (MANDATORY - task incomplete without these):
|
|
```
|
|
Scenario: Household member can view a shared product and update its shared inventory row
|
|
Tool: Bash
|
|
Steps: Run `cd backend && uv run pytest tests/test_products_auth.py -k 'shared_inventory_update or shared_product_visible' > ../.sisyphus/evidence/task-T5-product-sharing.txt`
|
|
Expected: Tests pass and evidence shows `200` assertions for shared view/update cases
|
|
Evidence: .sisyphus/evidence/task-T5-product-sharing.txt
|
|
|
|
Scenario: Household member cannot edit or delete another user's product
|
|
Tool: Bash
|
|
Steps: Run `cd backend && uv run pytest tests/test_products_auth.py -k 'cannot_edit_shared_product or cannot_delete_shared_product' > ../.sisyphus/evidence/task-T5-product-denied.txt`
|
|
Expected: Tests pass and evidence shows `403` or `404` assertions matching the route contract
|
|
Evidence: .sisyphus/evidence/task-T5-product-denied.txt
|
|
```
|
|
|
|
**Commit**: YES | Message: `feat(api): scope products and inventory by owner and household` | Files: `backend/innercontext/api/products.py`, `backend/innercontext/api/inventory.py`, related tests
|
|
|
|
- [x] T6. Retrofit remaining domain routes, LLM context, and jobs for per-user ownership
|
|
|
|
**What to do**: Update profile, health, routines, skincare, AI log, and LLM-context code so every query is user-scoped by default and admin override is explicit. `UserProfile` becomes one-per-user rather than singleton; `build_user_profile_context()` and product-context builders must accept the current user and only include accessible data. Routine suggestion/batch flows must use the current user's profile plus products visible under the owned/shared rules from T5. Ensure background pricing/job paths preserve `user_id` on products and logs, and that list endpoints never aggregate cross-user data for non-admins.
|
|
**Must NOT do**: Do not keep any `select(Model)` query unfiltered on an owned model, do not keep singleton profile lookups, and do not leak other users' AI logs or health data through helper functions.
|
|
|
|
**Recommended Agent Profile**:
|
|
- Category: `deep` - Reason: many routers and helper layers need consistent tenancy retrofits
|
|
- Skills: `[]` - Backend cross-module work only
|
|
- Omitted: `svelte-code-writer` - No Svelte component work in this task
|
|
|
|
**Parallelization**: Can Parallel: YES | Wave 2 | Blocks: T11 | Blocked By: T2, T3, T4
|
|
|
|
**References** (executor has NO interview context - be exhaustive):
|
|
- Pattern: `backend/innercontext/api/profile.py:27` - Current singleton profile route using `get_user_profile(session)`.
|
|
- Pattern: `backend/innercontext/api/llm_context.py:10` - Single-user helper that currently selects the most recent profile globally.
|
|
- Pattern: `backend/innercontext/api/health.py:141` - Medication and lab-result CRUD/list route layout.
|
|
- Pattern: `backend/innercontext/api/routines.py:674` - Routine list/create/suggest entry points that need scoped product/profile data.
|
|
- Pattern: `backend/innercontext/api/skincare.py:222` - Snapshot list/get/update/delete route structure.
|
|
- Pattern: `backend/innercontext/api/ai_logs.py:46` - AI-log exposure that must become owned/admin-only.
|
|
- Pattern: `backend/innercontext/services/pricing_jobs.py` - Background queue path that must preserve product ownership.
|
|
|
|
**Acceptance Criteria** (agent-executable only):
|
|
- [ ] Every non-admin router outside products/inventory scopes owned data by `user_id` before returning or mutating rows.
|
|
- [ ] `GET /profile` and `PATCH /profile` operate on the current user's profile, not the newest global profile.
|
|
- [ ] Routine suggestion and batch suggestion flows use only the current user's profile plus accessible products.
|
|
- [ ] AI logs are owned/admin-only, and background job/log creation stores `user_id` when applicable.
|
|
|
|
**QA Scenarios** (MANDATORY - task incomplete without these):
|
|
```
|
|
Scenario: Member only sees their own health, routine, profile, skin, and AI-log data
|
|
Tool: Bash
|
|
Steps: Run `cd backend && uv run pytest tests/test_tenancy_domains.py -k 'profile or health or routines or skincare or ai_logs' > ../.sisyphus/evidence/task-T6-domain-tenancy.txt`
|
|
Expected: Tests pass and evidence shows only owned/admin-allowed access patterns
|
|
Evidence: .sisyphus/evidence/task-T6-domain-tenancy.txt
|
|
|
|
Scenario: Routine suggestions ignore another user's products and profile
|
|
Tool: Bash
|
|
Steps: Run `cd backend && uv run pytest tests/test_routines_auth.py -k suggest > ../.sisyphus/evidence/task-T6-routine-scope.txt`
|
|
Expected: Tests pass and evidence shows suggestion inputs are scoped to the authenticated user plus shared inventory visibility rules
|
|
Evidence: .sisyphus/evidence/task-T6-routine-scope.txt
|
|
```
|
|
|
|
**Commit**: YES | Message: `feat(api): enforce ownership across health routines and profile flows` | Files: `backend/innercontext/api/profile.py`, `backend/innercontext/api/health.py`, `backend/innercontext/api/routines.py`, `backend/innercontext/api/skincare.py`, `backend/innercontext/api/ai_logs.py`, `backend/innercontext/api/llm_context.py`
|
|
|
|
- [x] T7. Implement SvelteKit OIDC login, callback, logout, refresh, and protected-session handling
|
|
|
|
**What to do**: Add server-only auth utilities under `frontend/src/lib/server/` and implement `Authorization Code + PKCE` in SvelteKit using Authelia discovery/token/userinfo endpoints. Create `/auth/login`, `/auth/callback`, and `/auth/logout` server routes. Extend `hooks.server.ts` to decrypt/load the app session, refresh the access token when it is near expiry, populate `event.locals.user` and `event.locals.session`, and redirect unauthenticated requests on all application routes except `/auth/*` and static assets. Use an encrypted HTTP-only cookie named `innercontext_session` with `sameSite=lax`, `secure` in production, and a 32-byte secret from private env.
|
|
**Must NOT do**: Do not store access or refresh tokens in `localStorage`, do not expose client secrets via `$env/static/public`, and do not protect routes with client-only guards.
|
|
|
|
**Recommended Agent Profile**:
|
|
- Category: `unspecified-high` - Reason: server-side SvelteKit auth flow with cookies, hooks, and redirects
|
|
- Skills: [`svelte-code-writer`] - Required for editing SvelteKit auth and route modules cleanly
|
|
- Omitted: `frontend-design` - This task is auth/session behavior, not visual redesign
|
|
|
|
**Parallelization**: Can Parallel: YES | Wave 2 | Blocks: T8, T10, T11 | Blocked By: T3
|
|
|
|
**References** (executor has NO interview context - be exhaustive):
|
|
- Pattern: `frontend/src/hooks.server.ts:1` - Current global request hook; auth must compose with existing Paraglide middleware rather than replacing it.
|
|
- Pattern: `frontend/src/app.d.ts:3` - Add typed `App.Locals`/`PageData` session fields here.
|
|
- Pattern: `frontend/src/routes/+layout.svelte:30` - App shell/navigation that will consume authenticated user state later.
|
|
- Pattern: `frontend/src/routes/products/suggest/+page.server.ts:4` - Existing SvelteKit server action style using `fetch`.
|
|
- External: `https://www.authelia.com/configuration/identity-providers/openid-connect/clients/` - Client configuration expectations for auth code flow and PKCE.
|
|
|
|
**Acceptance Criteria** (agent-executable only):
|
|
- [ ] SvelteKit exposes login/callback/logout server routes that complete the OIDC flow against Authelia and create/destroy `innercontext_session`.
|
|
- [ ] `hooks.server.ts` populates `event.locals.user`/`event.locals.session`, refreshes tokens near expiry, and redirects unauthenticated users away from protected pages.
|
|
- [ ] The callback flow calls backend auth sync before treating the user as signed in.
|
|
- [ ] Session cookies are HTTP-only and sourced only from private env/config.
|
|
|
|
**QA Scenarios** (MANDATORY - task incomplete without these):
|
|
```
|
|
Scenario: Login callback establishes an authenticated server session
|
|
Tool: Playwright
|
|
Steps: Navigate to `/products` while signed out, follow redirect to `/auth/login`, on the Authelia page fill the `Username` and `Password` fields using `E2E_AUTHELIA_USERNAME`/`E2E_AUTHELIA_PASSWORD`, submit the primary login button, wait for redirect back to the app, then save an accessibility snapshot to `.sisyphus/evidence/task-T7-login-flow.md`
|
|
Expected: Final URL is inside the app, the protected page renders, and the session cookie exists
|
|
Evidence: .sisyphus/evidence/task-T7-login-flow.md
|
|
|
|
Scenario: Expired or refresh-failed session redirects back to login
|
|
Tool: Playwright
|
|
Steps: Start from an authenticated session, replace the `innercontext_session` cookie with one containing an expired access token or invalidate the refresh endpoint in the browser session, reload `/products`, and save a snapshot to `.sisyphus/evidence/task-T7-refresh-failure.md`
|
|
Expected: The app clears the session cookie and redirects to `/auth/login`
|
|
Evidence: .sisyphus/evidence/task-T7-refresh-failure.md
|
|
```
|
|
|
|
**Commit**: YES | Message: `feat(frontend): add Authelia OIDC session flow` | Files: `frontend/src/hooks.server.ts`, `frontend/src/app.d.ts`, `frontend/src/lib/server/auth.ts`, `frontend/src/routes/auth/*`
|
|
|
|
- [x] T8. Refactor frontend data access, route guards, and shell state around the server session
|
|
|
|
**What to do**: Refactor frontend API access so protected backend calls always originate from SvelteKit server loads/actions/endpoints using the access token from `event.locals.session`. Convert browser-side direct `$lib/api` usage to server actions or same-origin SvelteKit endpoints, add a `+layout.server.ts` that exposes authenticated user data to the shell, and update `+layout.svelte` to show the current user role/name plus a logout action. Regenerate OpenAPI types if backend response models change and keep `$lib/types` as the canonical import surface.
|
|
**Must NOT do**: Do not keep browser-side bearer-token fetches, do not bypass the server session by calling backend APIs directly from components, and do not hardcode English auth labels without Paraglide message keys.
|
|
|
|
**Recommended Agent Profile**:
|
|
- Category: `unspecified-high` - Reason: SvelteKit route plumbing plus shell-state integration
|
|
- Skills: [`svelte-code-writer`] - Required because this task edits `.svelte` and SvelteKit route modules
|
|
- Omitted: `frontend-design` - Preserve the existing editorial shell instead of redesigning it
|
|
|
|
**Parallelization**: Can Parallel: YES | Wave 2 | Blocks: T11 | Blocked By: T7
|
|
|
|
**References** (executor has NO interview context - be exhaustive):
|
|
- Pattern: `frontend/src/lib/api.ts:25` - Current request helper branching between browser and server; replace with session-aware server usage.
|
|
- Pattern: `frontend/src/routes/+layout.svelte:63` - Existing app shell where user state/logout should appear without breaking navigation.
|
|
- Pattern: `frontend/src/routes/+page.server.ts` - Representative server-load pattern already used throughout the app.
|
|
- Pattern: `frontend/src/routes/skin/new/+page.svelte` - Existing browser-side API import to eliminate or proxy through server logic.
|
|
- Pattern: `frontend/src/routes/routines/[id]/+page.svelte` - Another browser-side API import that must stop calling the backend directly.
|
|
- Pattern: `frontend/src/routes/products/suggest/+page.server.ts:4` - Server action pattern to reuse for auth-aware fetches.
|
|
- API/Type: `frontend/src/lib/types.ts` - Keep as the only frontend import surface after any `pnpm generate:api` run.
|
|
|
|
**Acceptance Criteria** (agent-executable only):
|
|
- [ ] Protected backend calls in frontend code use the server session access token and no longer depend on browser token storage.
|
|
- [ ] Direct component-level `$lib/api` usage on protected paths is removed or wrapped behind same-origin server endpoints/actions.
|
|
- [ ] App shell receives authenticated user/session data from server load and exposes a logout affordance.
|
|
- [ ] `pnpm generate:api` is run if backend auth/API response changes require regenerated frontend types.
|
|
|
|
**QA Scenarios** (MANDATORY - task incomplete without these):
|
|
```
|
|
Scenario: Authenticated user navigates protected pages and sees session-aware shell state
|
|
Tool: Playwright
|
|
Steps: Log in, visit `/`, `/products`, `/profile`, and `/routines`; capture an accessibility snapshot to `.sisyphus/evidence/task-T8-protected-nav.md`
|
|
Expected: Each page loads without redirect loops, and the shell shows the current user plus logout control
|
|
Evidence: .sisyphus/evidence/task-T8-protected-nav.md
|
|
|
|
Scenario: Unauthenticated browser access cannot hit protected data paths directly
|
|
Tool: Playwright
|
|
Steps: Start from a signed-out browser, open a page that previously imported `$lib/api` from a component, attempt the same interaction, capture console/network output to `.sisyphus/evidence/task-T8-signed-out-network.txt`
|
|
Expected: The app redirects or blocks cleanly without leaking backend JSON responses into the UI
|
|
Evidence: .sisyphus/evidence/task-T8-signed-out-network.txt
|
|
```
|
|
|
|
**Commit**: YES | Message: `refactor(frontend): route protected API access through server session` | Files: `frontend/src/lib/api.ts`, `frontend/src/routes/**/*.server.ts`, `frontend/src/routes/+layout.*`, selected `.svelte` files, `frontend/src/lib/types.ts`
|
|
|
|
- [x] T9. Add admin-only household management API without a frontend console
|
|
|
|
**What to do**: Add a small admin-only backend router for household administration so the app can support real household sharing without a management UI. Provide endpoints to list local users who have logged in, create a household, assign a user to a household, move a user between households, and remove a membership. Enforce the v1 rule that a user can belong to at most one household. Do not manage identity creation here; Authelia remains the identity source, and only locally synced users may be assigned. Non-bootstrap users should remain household-less until an admin assigns them.
|
|
**Must NOT do**: Do not add Svelte pages for household management, do not let non-admins call these endpoints, and do not allow membership assignment for users who have never authenticated into the app.
|
|
|
|
**Recommended Agent Profile**:
|
|
- Category: `unspecified-high` - Reason: contained backend admin surface with sensitive authorization logic
|
|
- Skills: `[]` - Backend conventions already exist in repo
|
|
- Omitted: `frontend-design` - Explicitly no console/UI in scope
|
|
|
|
**Parallelization**: Can Parallel: YES | Wave 3 | Blocks: T11 | Blocked By: T1, T2, T3, T4
|
|
|
|
**References** (executor has NO interview context - be exhaustive):
|
|
- Pattern: `backend/main.py:50` - Router registration area; add a dedicated admin router here.
|
|
- Pattern: `backend/innercontext/api/profile.py:41` - Simple patch/upsert route style for small admin mutation endpoints.
|
|
- Pattern: `backend/innercontext/api/utils.py:9` - Error-handling pattern to preserve with tenant-aware replacements.
|
|
- API/Type: `backend/innercontext/models/profile.py:13` - Example of owned record exposed without extra wrapper models.
|
|
- Test: `backend/tests/conftest.py:34` - Dependency-override style for admin/member API tests.
|
|
|
|
**Acceptance Criteria** (agent-executable only):
|
|
- [ ] Backend exposes admin-only household endpoints for list/create/assign/move/remove operations.
|
|
- [ ] Membership moves preserve the one-household-per-user rule.
|
|
- [ ] Membership assignment only works for users already present in the local `users` table.
|
|
- [ ] Admin-route tests cover admin success, member denial, and attempted assignment of unsynced users.
|
|
|
|
**QA Scenarios** (MANDATORY - task incomplete without these):
|
|
```
|
|
Scenario: Admin can create a household and assign a logged-in member
|
|
Tool: Bash
|
|
Steps: Run `cd backend && uv run pytest tests/test_admin_households.py -k 'create_household or assign_member' > ../.sisyphus/evidence/task-T9-admin-households.txt`
|
|
Expected: Tests pass and evidence shows admin-only success cases
|
|
Evidence: .sisyphus/evidence/task-T9-admin-households.txt
|
|
|
|
Scenario: Member cannot manage households and unsynced users cannot be assigned
|
|
Tool: Bash
|
|
Steps: Run `cd backend && uv run pytest tests/test_admin_households.py -k 'forbidden or unsynced' > ../.sisyphus/evidence/task-T9-admin-households-denied.txt`
|
|
Expected: Tests pass and evidence shows `403`/validation failures for forbidden assignments
|
|
Evidence: .sisyphus/evidence/task-T9-admin-households-denied.txt
|
|
```
|
|
|
|
**Commit**: YES | Message: `feat(api): add admin household management endpoints` | Files: `backend/main.py`, `backend/innercontext/api/admin*.py`, related tests
|
|
|
|
- [x] T10. Update runtime configuration, validation scripts, deploy checks, and operator docs for OIDC
|
|
|
|
**What to do**: Update runtime configuration for both services so frontend and backend receive the new OIDC/session env vars at runtime, and document the exact Authelia client/server setup required. Keep nginx in a pure reverse-proxy role (no `auth_request`), but make sure forwarded host/proto information remains sufficient for callback URL generation. Extend `scripts/validate-env.sh` and deploy validation so missing auth env vars fail fast, and update `scripts/healthcheck.sh` plus `deploy.sh` health expectations because authenticated pages may now redirect to login instead of returning `200` for signed-out probes. Document bootstrap-admin env usage for the migration.
|
|
**Must NOT do**: Do not add proxy-level auth, do not require manual post-deploy DB edits, and do not leave deploy health checks assuming `/` must return `200` when the app intentionally redirects signed-out users.
|
|
|
|
**Recommended Agent Profile**:
|
|
- Category: `writing` - Reason: configuration, deployment, and operator-facing documentation dominate this task
|
|
- Skills: `[]` - Repo docs and service files are the governing references
|
|
- Omitted: `svelte-code-writer` - No Svelte component changes needed
|
|
|
|
**Parallelization**: Can Parallel: YES | Wave 3 | Blocks: T11 | Blocked By: T3, T7
|
|
|
|
**References** (executor has NO interview context - be exhaustive):
|
|
- Pattern: `nginx/innercontext.conf:1` - Current reverse-proxy setup that must remain proxy-only.
|
|
- Pattern: `deploy.sh:313` - Service-wait and health-check functions to update for signed-out redirects and auth env validation.
|
|
- Pattern: `deploy.sh:331` - Backend/frontend health-check behavior currently assuming public app pages.
|
|
- Pattern: `scripts/validate-env.sh:57` - Existing required-env validation script to extend with OIDC/session/bootstrap keys.
|
|
- Pattern: `scripts/healthcheck.sh:10` - Current frontend health check that assumes `/` returns `200`.
|
|
- Pattern: `systemd/innercontext.service` - Backend runtime env injection point.
|
|
- Pattern: `systemd/innercontext-node.service` - Frontend runtime env injection point.
|
|
- Pattern: `docs/DEPLOYMENT.md` - Canonical operator runbook to update.
|
|
|
|
**Acceptance Criteria** (agent-executable only):
|
|
- [ ] Backend and frontend runtime configs declare/document all required OIDC/session/bootstrap env vars.
|
|
- [ ] Deploy validation fails fast when required auth env vars are missing.
|
|
- [ ] Frontend health checks accept the signed-out auth redirect behavior or target a public route that remains intentionally available.
|
|
- [ ] Deployment docs describe Authelia client config, callback/logout URLs, JWKS/issuer envs, and bootstrap-migration envs.
|
|
|
|
**QA Scenarios** (MANDATORY - task incomplete without these):
|
|
```
|
|
Scenario: Deploy validation rejects missing auth configuration
|
|
Tool: Bash
|
|
Steps: Run `scripts/validate-env.sh` (or the deploy wrapper that calls it) with one required OIDC/session variable removed, and redirect output to `.sisyphus/evidence/task-T10-missing-env.txt`
|
|
Expected: Validation exits non-zero and names the missing variable
|
|
Evidence: .sisyphus/evidence/task-T10-missing-env.txt
|
|
|
|
Scenario: Signed-out frontend health behavior matches updated deployment expectations
|
|
Tool: Bash
|
|
Steps: Run the updated `scripts/healthcheck.sh` or deploy health-check path and save output to `.sisyphus/evidence/task-T10-health-check.txt`
|
|
Expected: Evidence shows a successful probe despite protected app routes (either via accepted redirect or a dedicated public health target)
|
|
Evidence: .sisyphus/evidence/task-T10-health-check.txt
|
|
```
|
|
|
|
**Commit**: YES | Message: `chore(deploy): wire OIDC runtime configuration` | Files: `nginx/innercontext.conf`, `deploy.sh`, `scripts/validate-env.sh`, `scripts/healthcheck.sh`, `systemd/*`, `docs/DEPLOYMENT.md`
|
|
|
|
- [ ] T11. Add shared auth fixtures, full regression coverage, and CI enforcement
|
|
|
|
**What to do**: Build reusable backend test fixtures for authenticated users, roles, households, and shared inventory, then add regression tests covering auth sync, unauthenticated access, admin/member authorization, household inventory sharing, routine/product visibility, and migration-sensitive ownership behavior. Use dependency overrides in tests instead of hitting a live Authelia server. Enable the existing backend CI job so these tests run in Forgejo, and make sure the final verification command set includes backend tests, lint, frontend check/lint/build, and any required API type generation.
|
|
**Must NOT do**: Do not depend on a live Authelia instance in CI, do not leave the backend test job disabled, and do not add a committed frontend browser test suite in this pass.
|
|
|
|
**Recommended Agent Profile**:
|
|
- Category: `unspecified-high` - Reason: broad regression coverage plus CI wiring across the monorepo
|
|
- Skills: `[]` - Existing pytest/CI patterns are sufficient
|
|
- Omitted: `playwright` - Browser QA stays agent-executed, not repository-committed
|
|
|
|
**Parallelization**: Can Parallel: NO | Wave 3 | Blocks: Final verification | Blocked By: T2, T3, T4, T5, T6, T7, T8, T9, T10
|
|
|
|
**References** (executor has NO interview context - be exhaustive):
|
|
- Pattern: `backend/tests/conftest.py:16` - Per-test DB isolation and dependency override technique.
|
|
- Pattern: `backend/tests/test_products.py:4` - Existing endpoint-test style to mirror for authz coverage.
|
|
- Pattern: `.forgejo/workflows/ci.yml:83` - Disabled backend test job that must be enabled.
|
|
- Pattern: `frontend/package.json:6` - Final frontend verification commands available in the repo.
|
|
- Pattern: `backend/pyproject.toml` - Pytest command/config surface for any new test files.
|
|
|
|
**Acceptance Criteria** (agent-executable only):
|
|
- [ ] Shared auth fixtures exist for admin/member identities, household membership, and shared inventory setup.
|
|
- [ ] Backend tests cover `401`, owner success, admin override, same-household shared inventory update, and different-household denial across representative routes.
|
|
- [ ] Forgejo backend tests run by default instead of being gated by `if: false`.
|
|
- [ ] Final command set passes: backend tests + lint, frontend check + lint + build, and API type generation only if required by backend schema changes.
|
|
|
|
**QA Scenarios** (MANDATORY - task incomplete without these):
|
|
```
|
|
Scenario: Full backend auth regression suite passes locally
|
|
Tool: Bash
|
|
Steps: Run `cd backend && uv run pytest > ../.sisyphus/evidence/task-T11-backend-regression.txt`
|
|
Expected: Evidence file shows the full suite passing, including new auth/tenancy tests
|
|
Evidence: .sisyphus/evidence/task-T11-backend-regression.txt
|
|
|
|
Scenario: CI config now runs backend tests instead of skipping them
|
|
Tool: Bash
|
|
Steps: Read `.forgejo/workflows/ci.yml`, confirm the backend-test job no longer contains `if: false`, and save a grep extract to `.sisyphus/evidence/task-T11-ci-enabled.txt`
|
|
Expected: Evidence shows the backend-test job is active and executes `uv run pytest`
|
|
Evidence: .sisyphus/evidence/task-T11-ci-enabled.txt
|
|
```
|
|
|
|
**Commit**: YES | Message: `test(auth): add multi-user regression coverage` | Files: `backend/tests/*`, `.forgejo/workflows/ci.yml`
|
|
|
|
## Final Verification Wave (4 parallel agents, ALL must APPROVE)
|
|
- [ ] F1. Plan Compliance Audit - oracle
|
|
- [ ] F2. Code Quality Review - unspecified-high
|
|
- [ ] F3. Real Manual QA - unspecified-high (+ playwright if UI)
|
|
- [ ] F4. Scope Fidelity Check - deep
|
|
|
|
## Commit Strategy
|
|
- Use atomic commits after stable checkpoints: Wave 1 foundation, Wave 2 application integration, Wave 3 infra/tests.
|
|
- Prefer conventional commits with monorepo scopes such as `feat(auth): ...`, `feat(frontend): ...`, `feat(api): ...`, `test(auth): ...`, `chore(deploy): ...`.
|
|
- Do not merge unrelated refactors into auth/tenancy commits; keep schema, auth flow, frontend session, and infra/test changes reviewable.
|
|
|
|
## Success Criteria
|
|
- Every protected route and API request resolves a concrete current user before touching owned data.
|
|
- Non-admin users cannot read or mutate records outside their ownership, except household-shared inventory entries.
|
|
- Household members can view/update shared inventory without gaining product edit rights.
|
|
- Existing single-user data survives migration and becomes accessible to the bootstrap admin account after first login.
|
|
- Frontend protected navigation/login/logout flow works without browser-stored bearer tokens.
|
|
- Backend test suite and CI catch auth regressions before deploy.
|