Files
microfish/docs/engineering-log/2026-08-24-saas-foundation-verification.md
Kunthawat Greethong 8b84378fe1 feat: SaaS foundation for CrowdSight
Elevate MiroFish/CrowdSight from single-container dev to a SaaS foundation:

- Local memory backend (Zep-compatible): memory services/models, local graph
  builder + updater, AgentActivity seam, import-boundary isolation; Zep stays
  default, local is opt-in behind MEMORY_BACKEND. Semantic parity not yet proven.
- Durable product persistence: projects/simulations/reports schema (migration
  0007) + tenant/owner-scoped ProductRepository + dual-write + scoped_project
  read-first + ArtifactStore abstraction; durable JobQueue + worker.py.
- SaaS hardening: durable RateLimiter (wired to login), UsageService (LLM
  accounting), redacted AuditService, idempotency, CORS allowlist, safe API
  errors, single-use PasswordResetService + endpoints (covers invite-pending).
- Exactly 3 roles (super_admin/admin/user) with tenant authz policy.
- Admin UI: GET/POST/PATCH /api/admin/users + GET/PUT /api/admin/settings
  (super-admin only, encrypted/masked); AdminView.vue + SettingsView.vue with
  admin/super-admin route guards, th/en i18n.
- Production deploy topology: multi-stage Dockerfile (frontend build + gunicorn
  wsgi + nginx SPA-proxy + supervisord worker), backend/wsgi.py, gunicorn dep.

Backend 197 passed; frontend 10 tests + build green. ruff unavailable (gap).
No commit of credentials; secrets handled via env/.env.example.
Deferred: Zep semantic A/B parity, object storage cutover, mobile QA, EasyPanel
container build of deploy topology.
2026-08-31 13:05:21 +07:00

5.0 KiB
Raw Blame History

SaaS Foundation Verification — 2026-08-24

Branch: feat/saas-foundation Status: in progress; not production-ready; no commit/push/deploy

Milestone

Closed the confirmed TaskManager app-state leak and extended the durable-task tenant boundary. Added a deterministic local end-to-end regression covering graph → entity reader → profiles → simulation config → report tools → persisted report.

Changes

  • TaskManager now binds to the current Flask app's session factory at construction time instead of relying on app-factory global singleton state.
  • In-memory TaskManager fallback remains test/legacy-only and isolated from app-bound managers; an app context without its configured factory now fails closed instead of using the class-global binding.
  • Shared SQLite engine creation enables PRAGMA foreign_keys=ON for every connection, including migration/test engines.
  • Durable get_task() and list_tasks() accept organization/owner filters and push predicates into SQLAlchemy queries.
  • Scoped task lookup and graph task listing pass tenant/owner predicates before the defensive metadata check.
  • Legacy asynchronous GraphBuilderService accepts session/tenant context and writes it into durable task metadata; authenticated graph build passes the captured context into the worker adapter.
  • Added test_local_graph_profile_simulation_report_is_persisted and test_graph_builder_async_task_keeps_organization_scope.

Verified evidence

  • TDD regression sequence: app-context fallback, SQLite-FK, reused-manager, explicit-mismatch, prebound-manager, background-use, auxiliary API auth/CSRF/idempotency, cross-route idempotency, multipart-content, and local Zep-import-boundary tests were observed RED where applicable, then passed GREEN; the full backend suite passed afterward (123 passed, four Alembic deprecation warnings).
  • Schema regression: 16 passed, including migration round-trip/model alignment, SQLite foreign-key enforcement, invalid org/owner rejection, delete actions, cross-app TaskManager binding, and background use without Flask context.
  • Auxiliary API regression: 8 passed; all five routes are authenticated, LLM mutations enforce CSRF/idempotency, safe error responses are redacted, and completed mutation responses replay correctly.
  • Idempotency regression: 5 passed; the request fingerprint separates method/path/query/body and includes multipart file content hashes across repeated file fields.
  • Local consumer-boundary regression: 5 fresh-import tests passed plus the activity-dict boundary regression; fresh subprocess tests prove app.services.local_graph_memory_updater, SimulationManager, SimulationRunner, ReportAgent, and GraphBuilderService do not eagerly load zep_cloud, while activity conversion uses a neutral shared contract.
  • Frontend contract tests: 10 passed.
  • npm run build: passed; existing Vite chunk/dynamic-import warnings remain.
  • PYTHONPATH= uv run python -m compileall -q app tests: passed.
  • Diff security scan over backend code: no shell=True, eval/exec, pickle loading, or hardcoded-secret assignment introduced.
  • ruff: unavailable; not counted as a pass.
  • Browser viewport capture at 320×568/500×768: not verified because Chrome requested an OS remote-debugging permission; no permission dialog was clicked.

Risks / open boundaries

  • The bounded local path is deterministic E2E; it does not prove semantic LLM parity or production worker durability.
  • Zep remains the default and compatibility runtime; consumer cutover is not complete.
  • Dockerfile still runs the development stack; no production WSGI/worker/object-storage topology has been verified.
  • ruff and real browser viewport QA remain blocked/unavailable.
  • Fresh independent bounded reviewer deleg_fb0f80b0: PASS (passed=true, empty security_concerns and logic_errors) after current-code app/factory mismatch probes for create/get/update/list paths, pooled and NullPool SQLite FK probes, migration round-trip, and ORM/migration parity. Suggestions are non-blocking and recorded in HANDOFF.md.
  • Fresh independent auxiliary reviewer deleg_d128494e: PASS (passed=true, empty security_concerns and logic_errors) after verifying all five routes, decorator ordering, safe errors, and 8/8 targeted tests. Wider SaaS/Zep/deployment review remains open.
  • Current-tree bounded local import-boundary reviewer deleg_65bce3de: PASS (passed=true, empty security_concerns and logic_errors) after running the focused boundary command (6 passed) and isolated probes for five fresh local imports, shared AgentActivity, explicit Zep paths, the graph_builder.Zep seam, and subprocess isolation. Suggestions are non-blocking; this closes only the import-boundary slice, not semantic parity or full consumer cutover.

Next exact action

Inspect the non-blocking reviewer suggestions only if they are useful for the next test-hardening pass. The bounded schema/TaskManager slice is approved; keep the broader SaaS/Zep/deployment blockers explicit in HANDOFF.md and continue consumer cutover.