Files
microfish/docs/HANDOFF.md
Kunthawat Greethong 333f6ccc6e fix: escape @ in login email placeholder (vue-i18n linked syntax)
Production /login rendered a blank page (browser console: SyntaxError: 10
through the vue-i18n parser). Root cause proved with a RED regression
(RES: vue-i18n public API reproduces 'Invalid linked format' code 10) plus an
independent reviewer: auth.emailPlaceholder="name@company.com" is invalid
vue-i18n linked-message syntax, so createI18n() throws a message-compilation
SyntaxError while LoginView renders t('auth.emailPlaceholder').

Fix: escape the literal at-sign as name{'@'}company.com in th and en so the
message compiles and the visible label is unchanged (name@company.com). Add an
all-translations regression that translates every string in th/en (objects
and arrays) through vue-i18n's public createI18n/global.t API and asserts the
visible placeholder value.

Verification:
- RED test failed at th:auth.emailPlaceholder (code 10) before the fix.
- Independent reviewer verified reproduction + fix, finished PASS.
- Frontend tests 11 passed; production build passed (index-B4oVHpLg.js).
- Chrome headless rendered the login card, Thai heading, and name@company.com
  from the production dist. Artifact checksum hash 3621155075b3d9245d2d05511aaf39b1b0cbcaeea local vs server.
2026-09-01 12:11:40 +07:00

93 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HANDOFF — MiroFish SaaS Migration
**Repo:** `/Users/kunthawat/Gitea/MiroFish`
**Branch:** `feat/saas-foundation`
**Base:** `89d04e7`
**Status:** SaaS foundation in progress; not production-ready; no commit/push yet
## Current outcome
The repository now has a tested identity/authentication foundation, tenant/owner guards around the main graph, simulation, report, and task lookup paths, durable operations schema/jobs/idempotency records, and a bounded local graph → profile → simulation → report persistence path. The original Zep runtime remains the default compatibility backend and legacy file-backed product persistence remains, so this is not yet a completed SaaS migration or production release.
## Delivered
- Thai/English locale policy with Thai default; legacy `zh` values normalize to Thai.
- Frontend API error sanitization and backend structured error envelopes.
- No full JSON request-body logging.
- SQLAlchemy models and Alembic migrations for organizations, users, memberships, and revocable sessions.
- Argon2 password hashing.
- HttpOnly session cookie plus signed CSRF cookie/header validation.
- Explicit credentialed CORS allowlist.
- Three roles only: `super_admin`, `admin`, `user`.
- `/api/auth/login`, `/api/auth/me`, `/api/auth/logout`.
- `/api/admin/users` list/create with role escalation prevention and response redaction.
- Project `organization_id`/`owner_user_id` metadata and scoped project reads/lists.
- Blueprint-wide auth and CSRF checks for simulation/report routes.
- Fail-closed scope checks for project, graph, simulation, report, and task IDs in path/query/body.
- Cross-tenant regression tests for project/simulation/report/task access.
- Frontend auth store using cookie sessions, `/login` view, protected workspace router guard, and localized Thai/English login copy.
- Production `/login` white screen root cause fixed locally (2026-09-01): raw `@` in `auth.emailPlaceholder` was invalid vue-i18n linked-message syntax and raised compiler code 10 during render. th/en now use `name{'@'}company.com`; an all-message compiler regression passes. Frontend **11 tests** + production build pass; Chrome rendered DOM contains the login UI. Pending: fresh review, push/redeploy, then live verification.
- Frontend report-status client corrected to POST `{ task_id, simulation_id }`.
- Durable local memory tables and a tenant-scoped SQLAlchemy repository.
- Durable `jobs`, idempotency records, and audit-log schema with migrations `0004_operations` through `0006_job_metadata`; `TaskManager` binds to the current app/session factory, fails closed when an app context has no factory, and has SQL-level tenant/owner filters.
- SQLite engines created by the shared DB helper enable foreign-key enforcement; regression coverage verifies invalid organization/owner references plus `SET NULL`/`CASCADE` delete behavior.
- GraphBuilder async adapter now carries organization/session context into durable task metadata.
- Deterministic local graph → profile → simulation-config → report-tools → persisted-report regression.
- Strict Pydantic extraction schema, bounded prompts, JSON validation, and deterministic LLM-result ingestion service.
- Golden parity fixtures and local adapters for entity filtering/context, quick search, panorama temporal inventory, and insight entity/relationship output shapes.
- Explicit `MEMORY_BACKEND=local` path now serves simulation entity routes and preparation; `SimulationManager.create_entity_reader()` fails closed when local mode has no injected scoped factory; default remains Zep during migration. Fresh bounded reviewer `deleg_029828b6` independently passed this exact reader/fallback slice with no security concerns or logic errors.
- The bounded local panorama/insight/entity-reader parity slice is covered by RED/GREEN tests; conflicting graph IDs fail closed and local simulation activity updater persists tenant-scoped episodes without constructing a Zep client.
- API safe-error regression gate covers simulation, graph, report, agent-group, and template boundaries; task/project/report/run state no longer persists raw exception text.
## Verification evidence
- Backend full suite after the latest app/factory isolation fixes, auxiliary/idempotency hardening, simulation/report/graph service import-boundary fixes, durable product-resource schema/repository, tenant-scoped `ArtifactStore`, durable `JobQueue`/`worker.py`, versioned redacted `PlatformSettings`, durable `RateLimiter`, durable LLM `UsageService`, durable redacted `AuditService`, and durable single-use `PasswordResetService`: **193 passed** with `PYTHONPATH= uv run pytest -q` from `backend` (2026-08-24); twenty-four Alembic deprecation warnings remain.
- Schema regression: **16 passed**, including Alembic upgrade → downgrade `0003_memory` → re-upgrade, operation-table column/index/FK inspection, SQLite PRAGMA enforcement, invalid org/owner rejection, delete actions, cross-app TaskManager binding checks, and background use without Flask context.
- Auxiliary API security regression: **8 passed**; every template/agent-group route requires auth, LLM mutations require CSRF + idempotency, safe internal errors are redacted, and both LLM mutations replay completed responses.
- Idempotency API regression: **5 passed**; same-route replay/conflict semantics remain, same-key cross-route/body replay is rejected, and multipart file content/repeated-file coverage is verified.
- Local consumer-boundary regression: **9 fresh-import tests passed** (`test_local_service_import.py`); fresh local imports of the updater, `SimulationManager`, `SimulationRunner`, `ReportAgent`, and `GraphBuilderService` do not eagerly load `zep_cloud`, `add_activity_from_dict` uses the shared activity contract without importing `zep_cloud` on the local path, and the reviewer-requested regression guards for explicit Zep-path imports, shared `AgentActivity` identity, and the `graph_builder.Zep` seam were added and pass. The activity-dict boundary regression also passes.
- Durable product-resource persistence: **21 product-persistence tests passed** (`test_product_persistence.py`). Migration `0007_product_resources` adds tenant/owner-scoped `projects`, `simulations`, and `reports` tables with FK cascade/SET-NULL, indexes, and a migration round-trip + `command.check` (no autogenerate drift). `ProductRepository` provides flush-only tenant-scoped create/get/list for all three with cross-tenant isolation verified. `sync_project`/`sync_simulation`/`sync_report` are idempotent dual-write bridges wired into project-creation, simulation-creation, and report-generation paths; `SimulationState` carries tenant scope and `_save_simulation_state` mirrors every status update into the durable table when a session factory is available, fail-closed when unscoped. `scoped_project` reads the durable table first (mapping back to the legacy `Project` shape) when the local backend is active, then falls back to filesystem, keeping existing routes intact. Read cutover/dual-write are gated to the local backend so the legacy Zep filesystem path is undisturbed.
- Tenant-scoped artifact store: **12 artifact-store tests passed** (`test_artifact_store.py`). `ArtifactStore` resolves tenant-scoped paths under a configured root, rejects traversal/absolute components (fail-closed), and exposes a store/read/exists/delete interface (`default_artifact_store()` is filesystem-backed; swapable for object storage later).
- Durable job queue + worker core: **10 tests passed** (`test_job_queue.py` 7, `test_worker.py` 3). `JobQueue` claims/executes/completes/fails durable `jobs` with optional organization scoping, a handler-registry dispatch (raises on unhandled operations for the worker to fail), and no double-claim; `worker.py` `run_once`/`run_loop` provide the portable worker entry point (no broker dependency yet).
- Versioned, redacted LLM settings: **4 tests passed** (`test_settings_service.py`). Migration `0008_platform_settings` + `SettingsService` store versioned global platform settings; the API key is encrypted with a `SECRET_KEY`-derived Fernet key (never stored/logged in plaintext, never in job snapshots), public reads return a masked value, and `snapshot_for_job()` captures the settings version for reproducible work. `cryptography` added as a direct dependency.
- Durable rate limiting: **6 tests passed** (`test_rate_limiter.py`). Migration `0009_rate_limit` + `RateLimiter` implement a durable sliding-window counter (survives restarts, per-key, no secrets), and are wired into the `/api/auth/login` route so excessive attempts return 429 (`api.tooManyAttempts` added in `th`/`en`).
- Durable LLM usage accounting: **4 tests passed** (`test_usage_service.py`). Migration `0010_usage_events` + `UsageService` record per-org/user LLM usage (model, input/output tokens, estimated cost) without storing prompt content or secrets, with org-scoped listing and total-cost totals.
- Durable redacted audit service: **3 tests passed** (`test_audit_service.py`). `AuditService` records tenant-scoped audit events while stripping sensitive keys (secrets, tokens, api keys, prompts) from details, and is wired into the `/api/auth/login` success path (`auth.login` event).
- Durable single-use password reset: **6 tests passed** (`test_password_reset.py`). Migration `0011_password_reset_tokens` + `PasswordResetService` issue expiring, single-use, hashed reset tokens (plaintext never stored), bump `auth_version` on consume to invalidate old sessions, and are exposed as `/api/auth/password-reset/request` + `/api/auth/password-reset/confirm` (enrollment-agnostic request prevents account enumeration; also covers invite-pending password setup).
- Frontend contract tests: **10 passed**; `npm run build`: **PASS**; existing chunk-size/dynamic-import warnings remain.
- Python `compileall`: **PASS**.
- Diff security scan: no introduced `shell=True`, `eval/exec`, or pickle loading in backend code; `git diff --check`: **PASS**.
- `ruff` is not installed in the backend environment; lint is skipped, not treated as a pass.
- Dev server `/login` smoke response: **PASS**. Real 320×568 and 500×768 browser capture is **not verified** because Chrome requested an OS remote-debugging permission and no permission dialog was clicked.
## Important incomplete work
1. **Zep replacement is not complete.** Local memory storage/extraction contracts and a bounded graph → profile → simulation → report persistence regression exist, but semantic LLM parity and full consumer cutover are not complete; Zep remains the default compatibility backend.
2. **Product persistence foundation is in place.** Durable tenant/owner-scoped `projects`/`simulations`/`reports` schema (migration `0007_product_resources`), a scoped `ProductRepository`, dual-write for creation and simulation run status, `scoped_project` read-first cutover (local backend), and a tenant-scoped `ArtifactStore` abstraction are added and tested. Legacy `ProjectManager`/`SimulationManager`/`ReportManager` filesystem paths remain authoritative-by-default for the Zep backend and for artifact payloads; full read cutover for simulation/report and migrating artifact payloads to object storage are deferred to the production storage/worker phase.
3. **Production worker/queue topology core is in place; broker/storage not decided.** A durable `JobQueue` (claim/dispatch/complete/fail, tenant-scoped) and a portable `worker.py` run-loop entry point exist and are tested, alongside a tenant-scoped `ArtifactStore`. Production decisions remain: choose a broker (Redis/RabbitMQ) or keep polling the durable table, choose object storage, and define the WSGI/worker/supervisord deployment topology. Background-thread execution in routes is not yet moved onto the durable queue.
4. **Admin user-management + super-admin settings UI base in place.** Backend `GET/POST/PATCH /api/admin/users` (role/status policy-guarded, status super-admin only) and `GET/PUT /api/admin/settings` (super-admin only, secret encrypted+masked via `SettingsService`). Frontend `AdminView.vue` (user mgmt) and `SettingsView.vue` (LLM settings form) are routed at `/admin` and `/admin/settings` with admin/super-admin role guards and th/en i18n. Invite self-setup UX, a connection-test endpoint, and full i18n/mobile review remain.
5. **LLM settings storage base is in place; admin/bootstrap UI not built.** A versioned, encrypted `PlatformSettings` store (`SettingsService` + migration `0008`) exists and is tested. Super-admin settings UI, provider preset validation, and connection-test endpoints are not yet built; active runtime still reads environment variables.
6. **Frontend active UI i18n gates pass; legacy `Process.vue` retired.** Frontend contract tests **10 passed** and the production build contains no CJK code points. The unused legacy `Process.vue` (Chinese copy) was retired — the router's `name: 'Process'` uses `MainView.vue`. Real mobile 320×568/500×768 visual capture requires a full-stack + authenticated browser session and is not yet verified; a standalone `vite preview` of the SPA renders a blank mount (needs backend bootstrap), which is not treated as a pass.
7. **Docker deployment drafted as production multi-service; container build not yet proven.** Dockerfile is rebuilt as a production multi-service image (multi-stage frontend build, python + gunicorn `wsgi:app`, nginx serving the built SPA with history fallback and `/api`→gunicorn proxy, supervisord running backend + durable worker). `backend/wsgi.py` and `gunicorn>=21` were added. Local smoke test (gunicorn + built-dist static serving) passed — `/health` OK, assets rebuilt — but the nginx SPA-fallback + proxy + worker-poll path must be verified by building the image in EasyPanel; no Docker runtime is available on this Mac.
8. **SaaS hardening mostly complete.** Durable rate limiting (wired to login), durable LLM usage/cost accounting, a durable redacted audit service, and durable single-use password reset (which also covers invite-pending setup) are added and tested. Still open: a complete resource authorization matrix across every role × endpoint, a full retryable-mutation inventory, and long-running-job review.
9. **Auxiliary API hardening is verified.** Fresh reviewer `deleg_d128494e` returned `passed=true` with empty `security_concerns` and `logic_errors`; current auxiliary security tests pass **8/8**. This closes only the template/agent-group slice, not the broader SaaS gate.
10. **Bounded schema/TaskManager review passed.** Fresh reviewer `deleg_fb0f80b0` returned `passed=true` with empty `security_concerns` and `logic_errors` after current-code runtime app/factory mismatch probes, SQLite pooled/NullPool foreign-key checks, ORM/migration parity, and migration round-trip checks. Its suggestions are non-blocking: expand mismatch coverage across every TaskManager operation, broaden pooled/NullPool assertions in tests, and assert full ORM/migration signatures. This closes only the exact schema/TaskManager slice; broad reviewer `deleg_949ab2f4` timed out and the wider SaaS gate remains open.
11. **The current bounded local import-boundary review passed.** Fresh reviewer `deleg_65bce3de` returned `passed=true` with empty `security_concerns` and `logic_errors`; it ran the focused boundary command with **6 passed** and isolated probes for five fresh local imports, shared `AgentActivity`, explicit Zep paths, the `graph_builder.Zep` seam, and subprocess isolation. This closes only the bounded import-boundary slice; semantic parity, complete consumer cutover, and the wider SaaS gate remain open.
## Next implementation order
1. Keep the current staged snapshot and re-run verification after any further edits.
2. Extend golden parity and tenant-negative coverage to the remaining local consumers and simulation-context/report E2E path.
3. Switch graph builder, entity reader, tools, profile generator, and report agent to the local memory adapter.
4. Remove Zep dependency/config only after parity and migration tests pass.
5. Add admin/super-admin UI and settings policy.
6. Finish production worker/deployment hardening and run a fresh end-to-end tenant matrix.
## Guardrails
- Exactly three role identifiers: `super_admin`, `admin`, `user`.
- Browser never receives LLM/API secrets.
- Backend authorization is authoritative; frontend guards are convenience only.
- Do not claim semantic LLM/Zep parity without golden-fixture evidence.
- Do not commit or deploy while the independent review gate remains unresolved.