Memora — storefront
Diario asistido por IA + crónica colaborativa. Dossier completo de producto:
doc/memora/
Sprint 0 — qué incluye este scaffold
✅ Estructura Astro + React + Tailwind alineada con storefronts/codex y storefronts/radia
✅ wrangler.toml con bindings AI / DB / R2 / Vectorize y 2 crons
✅ 6 migraciones D1 (migrations/0001…0006)
✅ Helpers de retrieval híbrida (src/lib/retrieval/): parseQuery, dense (bge-m3), sparse (FTS5), structured, rrf, cache, visibility, y orquestador hybridSearch()
✅ Auth Google + JWT (functions/api/auth/google.js, me.js) reusando patrón RADIA
✅ Middleware global con CORS + inyección de sesión
✅ Endpoints stub REST: projects, entries, stt, assets/upload, retrieval/search, chronicle/sessions
✅ UI base: landing con dual-CTA, login con Google Identity Services, app shell con ProjectSwitcher, formulario de creación de proyecto
❌ Pendiente Sprint 1 (ver doc/memora/ROADMAP.md):
- Composer Diario y Composer Crónica
- IA-entrevistador en streaming (SSE)
chronicle/split(segmentar audio largo en N entries)- Timeline + capítulos + EntryCard
- Worker de embeddings (consume
memora_embedding_job) - Cron: asistente semanal Diario + detector de huecos Crónica
- Vista de testigo
/w/[token] - TTS para audiolibro (Workers AI Aura → Groq fallback)
Estructura
storefronts/memora/
├── astro.config.mjs
├── package.json
├── tailwind.config.mjs
├── tsconfig.json
├── wrangler.toml
├── public/
│ ├── favicon.svg
│ └── manifest.webmanifest
├── migrations/
│ ├── 0001_users_projects.sql
│ ├── 0002_periods_entries_assets.sql
│ ├── 0003_persons.sql
│ ├── 0004_witnesses_chapters.sql
│ ├── 0005_chronicle_sessions.sql
│ └── 0006_fts_cache_jobs.sql
├── src/
│ ├── env.d.ts
│ ├── styles/global.css
│ ├── layouts/BaseLayout.astro
│ ├── pages/
│ │ ├── index.astro # landing
│ │ ├── login.astro # Google Identity
│ │ ├── app.astro # SPA shell (auth gate)
│ │ └── app/new-project.astro
│ ├── components/
│ │ └── ProjectSwitcher.tsx
│ └── lib/
│ └── retrieval/
│ ├── index.ts # hybridSearch() — única puerta
│ ├── types.ts
│ ├── parseQuery.ts
│ ├── dense.ts # bge-m3 + Vectorize
│ ├── sparse.ts # FTS5 + bm25
│ ├── structured.ts
│ ├── rrf.ts
│ ├── cache.ts
│ └── visibility.ts # canSee + visibilityWhere()
└── functions/ # Cloudflare Pages Functions
├── _middleware.js # CORS + inyecta data.user / data.witness
├── lib/
│ ├── jwt.js # HS256 + witness tokens
│ ├── auth.js # nanoid, getUserFromRequest, resolveRoleInProject
│ └── response.js
└── api/
├── auth/
│ ├── google.js
│ └── me.js
└── memora/
├── projects/index.js
├── entries/index.js
├── stt.js # Workers AI Whisper → Groq fallback
├── assets/upload.js # R2
├── retrieval/search.ts
└── chronicle/
├── sessions/index.js
├── interview.js # stub S0
└── split.js # stub S0
Bootstrap (NO ejecutar todavía sin coordinarlo)
⚠️ Este Sprint 0 es solo código local. No se han creado recursos en Cloudflare.
Cuando quieras crearlos:
# Desde la raíz del repo (ProjectOS/)
cd storefronts/memora
npm install
# 1. D1
npx wrangler d1 create memora-db
# → copia el database_id devuelto a wrangler.toml
# 2. R2
npx wrangler r2 bucket create memora-assets
# 3. Vectorize (1024 dims, cosine — bge-m3)
npx wrangler vectorize create memora-entries --dimensions=1024 --metric=cosine
# 4. Migraciones (local)
foreach ($f in Get-ChildItem migrations\*.sql | Sort-Object Name) {
npx wrangler d1 execute memora-db --local --file=$f.FullName
}
# 4b. Migraciones (remoto, después)
foreach ($f in Get-ChildItem migrations\*.sql | Sort-Object Name) {
npx wrangler d1 execute memora-db --remote --file=$f.FullName
}
# 5. Pages project
npx wrangler pages project create memora --production-branch=main
# 6. Build + deploy
npm run build
npm run deploy
Secrets a configurar en Cloudflare Pages → Settings → Environment Variables
Nota — Stack compartido cadences.app: Memora reusa los mismos secrets de cuenta que RADIA / codex / etc. Si despliegas dentro de la organización Cadences, las claves marcadas con 🔁 ya están heredadas y no hay que volver a configurarlas (el JWT incluye
app:'memora'para evitar reuso cross-app).
| Secret | Origen |
|---|---|
🔁 GOOGLE_CLIENT_ID |
Compartido cadences (71544957807-...) |
🔁 GOOGLE_CLIENT_SECRET |
Compartido cadences |
🔁 JWT_SECRET |
Compartido cadences (fallback dev: projectos-jwt-secret-default) |
🔁 DEEPSEEK_API_KEY |
Compartido cadences (LLM principal) |
🔁 GROQ_API_KEY |
Compartido cadences (vision + STT fallback) |
🔁 RESEND_API_KEY |
Compartido cadences (transactional email) |
🆕 WITNESS_TOKEN_SECRET |
openssl rand -base64 64 — propio de Memora |
🆕 CRON_SECRET |
openssl rand -base64 32 — protege endpoint del drainer (ver Cron) |
Cron / jobs de embeddings
Los crons declarados en wrangler.toml (*/5 * * * *, etc.) no son
ejecutados automáticamente por Cloudflare Pages Functions con file-routing —
son sólo declarativos.
Estrategia por defecto: drenado oportunista desde el cliente — no requiere
cron externo. La app llama a POST /api/memora/jobs/embeddings/tick (auth con
JWT del usuario, batch máx 30, scoped a sus proyectos) cuando:
- Carga
/app - Vuelve a la pestaña tras estar inactivo (
visibilitychange) - Acaba de crear una entry (debounce 3s)
Implementado en src/lib/client/embeddingsTicker.ts
con throttle de 60s entre ticks. Si Memora tiene cualquier user activo a lo
largo del día, los embeddings se mantienen al día solos.
Opcional (recomendado para alta concurrencia o usuarios poco activos):
schedule HTTP externo al endpoint admin POST /api/memora/jobs/embeddings/drain
con header X-Cron-Secret: <CRON_SECRET> y body {"batchSize": 50}. Útil para:
- Worker externo con
scheduled()que hacefetch()cada 5 min. - cron-job.org / GitHub Actions programados.
Ambos endpoints comparten la misma lógica (functions/lib/embeddings.js):
toman jobs pending o failed con attempts < 3, embeben con
@cf/baai/bge-m3 y hacen upsert en Vectorize. Son idempotentes y
lock-free vía UPDATE ... SET status='processing'.
Desarrollo local
cd storefronts/memora
npm install
npm run dev
Para probar funciones con bindings reales (D1, R2, Vectorize, AI):
npm run build
npx wrangler pages dev dist --d1=DB --r2=ASSETS_BUCKET --ai
Dominio
Apuntar memora.cadences.app → Pages project memora (CNAME desde Cloudflare Dashboard).
Checklist Sprint 0 — verificación
-
npm installcompleta sin errores -
npm run buildgeneradist/(Astro + Tailwind + React compilan) -
npx wrangler d1 execute memora-db --local --file=migrations/0001_users_projects.sqlaplica sin errores - (sin tocar prod)
npx wrangler pages dev distsirve la landing -
/logincarga el botón de Google -
POST /api/auth/googledevuelve token JWT (requiere DB local + GOOGLE_CLIENT_ID + JWT_SECRET en.dev.vars) -
GET /api/auth/mecon Bearer devuelve el user - Documentación dossier
doc/memora/revisada y sin discrepancias con esquema
Próximo paso (Sprint 1)
Ver doc/memora/ROADMAP.md — sección "Sprint 1: dos modos en paralelo (gporto beta)".