Context
Everything downstream (reports, auto-reports, billing meters) feeds on ingested events. Mirror loghq's ingestion discipline (routes/logs.ts, app/Logs/normalize.ts, app/Errors/{ingest,limits}.ts, docs/ingest.md) but for analytics events and metrics rather than log lines.
Scope
Eventmodel (append-only stream): project-scoped,name(dot taxonomy, e.g.commerce.order.created),occurred_at, arbitrary bounded JSONproperties, optionalvalue/currency, optionaluser_key(customer-supplied pseudonymous id for uniques/retention),session_key, indexes chosen for time-range + name queries. Schema from the model only (#5).POST /ingestwithX-ReportsHQ-Key: rhq_...(write-only key from #4): batched{ events: [...] }, 512 KB body cap, batch-size cap, per-event field bounds and normalization (drop-with-count, never 500 on bad rows), response201 { ok, stored, dropped, skipped }.- Rate limits: per-project and per-IP over a sliding window (port loghq's Redis-or-process limiter or use
ts-rate-limiterfrom~/Code/Libraries- whichever is stronger; ifts-rate-limiteris missing a capability, improve it upstream +bun run release:patch). - Read API under bearer auth:
GET /api/projects/{id}/events?name=&from=&to=&before=with keyset pagination (loghq pattern, no offset pagination). - Standard event taxonomy doc
docs/events.md: the reservedcommerce.*,user.*,cms.*names with required/optional properties per event. This doc is the contract for the SDKs (#7, #8) and the auto-report templates (#14). - Wire contract doc
docs/ingest.md(request/response shapes, limits, error semantics - match the completeness of loghq's). - Feature tests: happy path, oversize body, bad key, mixed-validity batch, rate-limit 429s, cross-project isolation.
Acceptance
- A
curlbatch from the docs works verbatim against a dev server; tests cover every documented limit.
Depends on #4. Blocks #7, #8, #10, #14, #18. Part of #1 (M2 Accounts and data). Ground rules: see #1.