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>
7.6 KiB
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)