Files
mizan/ISSUES.md
Ryth Azhur 499aa0e038 Add ISSUES.md — expert review findings across 7 domains
7 critical, 13 high, 18 medium issues identified by:
Cloudflare, Serverless, Vercel, React Query, Django, Laravel, Vue/Svelte

Critical: scoped cache purge broken, initSession swallows errors,
SSR hydration never injected, SSR bridge thread-unsafe + leaks
processes, no loading/error states in kernel, view-path mutations
skip cache purge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:18:57 -04:00

156 lines
7.6 KiB
Markdown

# Mizan — Known Issues
Identified by domain expert review (Cloudflare, Serverless, Vercel, React Query, Django, Laravel, Vue/Svelte).
## Critical
### C1. Scoped cache purge doesn't pass user_id/rev
**File:** `mizan-django/src/mizan/client/executor.py` — cache_purge call in function_call_view
The scoped purge recomputes the HMAC with `user_id=None, rev=0`, but the entry was stored with the actual user_id and rev. The key never matches. Stale data persists until TTL expires.
### C2. initSession swallows errors permanently
**File:** `mizan-runtime/src/index.ts` — initSession()
If the session init fetch fails, the promise resolves (catch swallows). Every subsequent call returns the resolved promise. CSRF is broken for the page lifetime with no recovery.
### C3. SSR hydration data never written
**File:** `mizan-django/src/mizan/ssr/backend.py` — MizanTemplate.render()
Generated code checks `window.__MIZAN_SSR_DATA__` but nothing injects it. The `<script>` tag with serialized data is missing from the SSR output. Client re-fetches everything, defeating SSR.
### C4. SSR bridge stdin.write not thread-safe
**File:** `mizan-django/src/mizan/ssr/bridge.py` — render()
The lock protects `_counter` but not the `stdin.write()`. Concurrent Django threads interleave writes, producing garbled JSON. Bun worker returns parse errors.
### C5. SSR bridge orphans Bun processes
**File:** `mizan-django/src/mizan/ssr/bridge.py` — no cleanup
No atexit handler, no signal handler. Django auto-reloader, gunicorn recycling, and SIGTERM all leak Bun processes.
### C6. No loading/error/stale states in runtime
**File:** `mizan-runtime/src/index.ts` — context registry
The kernel stores only `{params, refetch}`. No `data`, `status`, `error`. Every adapter independently reinvents loading tracking. No stale-while-revalidate possible.
### C7. View-path mutations don't purge origin cache
**File:** `mizan-django/src/mizan/client/executor.py` — view-path return branch
Sets `X-Mizan-Invalidate` header but never calls `cache_purge`. Without Edge deployed, stale data persists.
## High
### H1. pendingScoped Map overwrites
**File:** `mizan-runtime/src/index.ts` — invalidate()
Two scoped invalidations for the same context in one microtask — second `set()` overwrites first. Only one param set gets refetched.
### H2. JSON.stringify params key is order-dependent
**File:** `mizan-runtime/src/index.ts` — registerContext + invalidation
`{a:1, b:2}` !== `{b:2, a:1}` as JSON strings. Scoped invalidation misses targets depending on property order.
### H3. No retry logic
**File:** `mizan-runtime/src/index.ts` — mizanFetch, mizanCall
Single network failure is permanent. No exponential backoff, no retry count.
### H4. Named contexts refetch unconditionally, ignore SSR data
**File:** `mizan-django/generate/generator/lib/adapters/react.mjs` — useEffect
Global context checks `if (!data) refetch()`. Named contexts always refetch, discarding hydrated state.
### H5. Mutation hooks expose no loading/error state
**File:** `mizan-django/generate/generator/lib/adapters/react.mjs` — mutation template
Returns bare `useCallback`. No `isPending`, `error`, `isSuccess`. Every handler needs manual try/catch/finally.
### H6. refreshContext uses call() (mutation endpoint)
**File:** `mizan-react/src/context.tsx` — refreshContext callback
Calls `POST /call/` for context refresh. Should use `GET /ctx/<name>/`.
### H7. Redis SCAN blocks request path at scale
**File:** `mizan-django/src/mizan/cache/backend.py` — delete_by_prefix
Synchronous SCAN loop with 1K batch. At 1M keys: multi-second blocking on the mutation response.
### H8. Svelte codegen uses Svelte 4 stores
**File:** `mizan-django/generate/generator/lib/adapters/svelte.mjs`
Uses `writable`/`derived` from `svelte/store`. Svelte 5 (stable since 2024) uses `$state`/`$derived` runes.
### H9. Svelte destroy() not auto-called
**File:** `mizan-django/generate/generator/lib/adapters/svelte.mjs`
`registerContext` is called at store creation. `destroy()` is returned but never called automatically. Memory leak if user forgets `onDestroy`.
### H10. _meta ClassVar shared dict fragility
**File:** `mizan-django/src/mizan/client/function.py` — _create_server_function
When `meta` is empty, `FunctionWrapper._meta` is the same object as `ServerFunction._meta`. One in-place mutation pollutes all functions.
### H11. Cross-language str() vs String() divergence
**File:** `mizan-django/src/mizan/cache/keys.py` — derive_cache_key
Python `str(True)` = `"True"`, JS `String(true)` = `"true"`. Cache keys diverge for boolean/None params.
### H12. Forms triggerValidation captures stale data
**File:** `mizan-react/src/forms.ts` — triggerValidation useCallback
Debounced touch() fires validation against the `data` value from when `useCallback` was created, not current data.
### H13. Forms isValid true with untouched required fields
**File:** `mizan-react/src/forms.ts` — isValid useMemo
Only checks touched fields. 5 required fields, 2 touched, 0 errors → `isValid = true`.
## Medium
### M1. SSR bridge not fork-safe
gunicorn prefork shares stdin/stdout file descriptors and Redis connections.
### M2. cache_purge_user() spec'd but not implemented
No way to purge all cache entries for one user on permission change.
### M3. No garbage collection for context entries
Runtime `contexts` Map grows monotonically. No TTL, no eviction.
### M4. No cross-tab invalidation
No BroadcastChannel, no storage events. Logout in tab 1 doesn't affect tab 2.
### M5. React 18 Strict Mode double-fetch
useEffect runs twice in dev mode. Every context provider fires two requests on mount.
### M6. No request deduplication
Two components mounting the same context fire parallel identical fetches.
### M7. SSR worker module cache never invalidates
Dynamic imports cached in Map forever. No reload mechanism.
### M8. Vue injection key not exported
Consumers can't inject directly without using generated composables.
### M9. Vue onMounted won't pre-fetch in Vue SSR
Needs `onServerPrefetch` for Nuxt/Vue SSR. Only works with Mizan's bridge.
### M10. Svelte should use setContext/getContext
Module-level stores don't scope to component tree. Two instances share state.
### M11. execute_function return type annotation wrong
Says `FunctionResult | FunctionError`, actually returns `HttpResponseBase` for view-path.
### M12. render_strategy heuristic uses hardcoded param names
`user_id`, `owner_id` etc. — misses `member_id`, `customer_id`, non-English names.
### M13. initSession called for token-auth requests
Wastes a GET /session/ round-trip for JWT/MWT apps that don't need CSRF.
### M14. Vue watch imported but unused
Params not watched — if reactive params change, context doesn't refetch.
### M15. Vue mutation composables are just re-exports with misleading `use` prefix
`export const useXxx = callXxx` — not a real composable, breaks Vue naming convention.
### M16. Svelte mutation imports bypass Stage 1 index
Imports from `'../mutations/xxx'` directly instead of `'../index'`.
### M17. Side effects in React state updater
Context listeners called inside `setContextStore()` updater — fires multiple times in concurrent mode.
### M18. registerContext cleanup crashes if Map deleted
`contexts.get(name)!.delete(key)` — non-null assertion throws if Map was cleared.
## Testing Gaps
- No test for scoped purge with user_id/rev mismatch (C1)
- No test for view-path cache purge (C7)
- No test for concurrent SSR renders (C4)
- No test for fork-safety (M1)
- No cross-language boolean/None pin tests (H11)
- No test for initSession failure recovery (C2)
- No test for SSR hydration data injection (C3)
- No test for pendingScoped overwrite (H1)
- No test for JSON key ordering mismatch (H2)