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

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)