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>
This commit is contained in:
155
ISSUES.md
Normal file
155
ISSUES.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user