Architecture rework: fix protocol bugs, add origin-side cache, document spec
8-expert review identified 3 bugs in shipped code (Vary header hallucination, fn/function wire key mismatch, max-age=0 defeating PSR) — all fixed with tests updated across Python and TypeScript. Added: manifest version field, affects validation, wire format convention, origin-side cache module (HMAC key derivation, MemoryCache + RedisCache backends, reverse index for scoped invalidation, executor integration). 16 known issues documented in cache/KNOWN_ISSUES.md from expert review — critical items (user_id not passed, purge race condition, no Redis error handling) to be fixed in follow-up. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -72,8 +72,7 @@ export async function handleContextFetch(
|
||||
body: results,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'public, max-age=0, stale-while-revalidate=300',
|
||||
'Vary': 'Authorization, Cookie',
|
||||
'Cache-Control': 'public, max-age=0, s-maxage=31536000',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
import type { EdgeManifest } from './types'
|
||||
import { getAllFunctions, getContextGroups, getContextParamNames } from './registry'
|
||||
|
||||
// Both camelCase and snake_case forms included for cross-language matching.
|
||||
// Wire format is snake_case (protocol rule); camelCase is the TS-local convention.
|
||||
const USER_SCOPED_PARAMS = new Set(['userId', 'user', 'ownerId', 'accountId', 'user_id', 'owner_id', 'account_id'])
|
||||
|
||||
export function generateManifest(baseUrl = '/api/mizan'): EdgeManifest {
|
||||
const groups = getContextGroups()
|
||||
const allFunctions = getAllFunctions()
|
||||
const manifest: EdgeManifest = { contexts: {}, mutations: {} }
|
||||
const manifest: EdgeManifest = { version: 1, contexts: {}, mutations: {} }
|
||||
|
||||
// Contexts
|
||||
for (const [ctxName, fnNames] of Object.entries(groups)) {
|
||||
|
||||
@@ -56,6 +56,7 @@ export interface ManifestMutation {
|
||||
}
|
||||
|
||||
export interface EdgeManifest {
|
||||
version: number
|
||||
contexts: Record<string, ManifestContext>
|
||||
mutations: Record<string, ManifestMutation>
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ describe('Edge Compatibility', () => {
|
||||
test('context GET is cacheable', async () => {
|
||||
const r = await handleContextFetch('user', { userId: '5' })
|
||||
expect(r.headers['Cache-Control']).toContain('public')
|
||||
expect(r.headers['Cache-Control']).toContain('stale-while-revalidate')
|
||||
expect(r.headers['Cache-Control']).toContain('s-maxage')
|
||||
expect(r.headers['Cache-Control']).not.toContain('no-store')
|
||||
})
|
||||
|
||||
@@ -70,14 +70,6 @@ describe('Edge Compatibility', () => {
|
||||
expect(r.headers['Cache-Control']).toBe('no-store')
|
||||
})
|
||||
|
||||
// ── Vary header ────────────────────────────────────────────────────
|
||||
|
||||
test('Vary header present on context GET', async () => {
|
||||
const r = await handleContextFetch('user', { userId: '5' })
|
||||
expect(r.headers['Vary']).toContain('Authorization')
|
||||
expect(r.headers['Vary']).toContain('Cookie')
|
||||
})
|
||||
|
||||
// ── X-Mizan-Invalidate header ──────────────────────────────────────
|
||||
|
||||
test('mutation response includes invalidation header', async () => {
|
||||
@@ -195,6 +187,7 @@ describe('Manifest', () => {
|
||||
test('manifest matches expected structure', () => {
|
||||
const m = generateManifest()
|
||||
|
||||
expect(m.version).toBe(1)
|
||||
expect(m.contexts.user).toBeDefined()
|
||||
expect(m.contexts.user.endpoints).toEqual(['/api/mizan/ctx/user/'])
|
||||
expect(m.contexts.user.params).toContain('userId')
|
||||
|
||||
Reference in New Issue
Block a user