glueful / thallo-analytics
Product-analytics fact store for Thallo: consumes lifecycle events, owns its own facts, as a removable capability pack.
Requires
- php: ^8.3
- glueful/extension-contracts: *
- glueful/framework: ^1.65.0
- glueful/thallo-contracts: v1.0.0-beta.59
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v1.0.0-beta.59
- v1.0.0-beta.58
- v1.0.0-beta.57
- v1.0.0-beta.56
- v1.0.0-beta.55
- v1.0.0-beta.54
- v1.0.0-beta.53
- v1.0.0-beta.52
- v1.0.0-beta.51
- v1.0.0-beta.50
- v1.0.0-beta.49
- v1.0.0-beta.48
- v1.0.0-beta.47
- v1.0.0-beta.46
- v1.0.0-beta.45
- v1.0.0-beta.44
- v1.0.0-beta.43
- v1.0.0-beta.42
- v1.0.0-beta.41
- v1.0.0-beta.40
- v1.0.0-beta.39
- v1.0.0-beta.38
- v1.0.0-beta.37
- v1.0.0-beta.36
- v1.0.0-beta.35
- v1.0.0-beta.34
- v1.0.0-beta.33
- v1.0.0-beta.32
- v1.0.0-beta.31
- v1.0.0-beta.30
- v1.0.0-beta.29
- v1.0.0-beta.28
- v1.0.0-beta.27
- v1.0.0-beta.26
- v1.0.0-beta.25
- v1.0.0-beta.24
- v1.0.0-beta.23
- v1.0.0-beta.22
- v1.0.0-beta.21
This package is auto-updated.
Last update: 2026-09-24 14:38:43 UTC
README
A self-contained product-analytics fact store for Thallo. It consumes content, collection, and auth lifecycle events, owns its own privacy-minimized facts (raw facts
- daily rollups + distinct-actor presence), and exposes a gated admin read API — packaged as a
capability pack that depends only on the framework and
glueful/thallo-contracts.
It answers "what's happening in the content/data" and "who's using it," and is deliberately distinct
from two things that already capture data: the framework's metrics capability (raw HTTP traffic —
ops, not product) and audit_logs (immutable per-action forensics). Analytics stores aggregated
trends; audit and analytics are independent consumers of the same pure events.
What it provides
- Three tables (hybrid model — raw facts are canonical, rollups serve fast reads):
Table Role analytics_factsAppend-only raw event rows (source of truth); pruned after the retention window. Raw actor_idlives only here.analytics_daily(day, event, subject) → count, UPSERT-incremented per fact; a__total__sentinel row is the per-event daily total, low-cardinality subjects (collections, content types) also get a breakdown row. Kept forever.analytics_active_actors(day, metric, actor_type, actor_id_hash)— a salted HMAC of the actor id, never the raw value. "Active users / day" = distinct rows. Kept forever, privacy-minimized. AnalyticsRecorder— the single, synchronous, best-effort write chokepoint (never throws into the request). Writes the fact, atomically increments the daily rollups (ON CONFLICT), and records a distinct active user (humans only —adminnormalized touser, api-keys/system excluded).- Ingestion — the pack subscribes framework auth events (
login/logout/login_failed) under a strict token/PII allow-list (never reads token accessors; failed logins are count-only). A bridge listener in core (Thallo\Core\Analytics\AnalyticsBridgeListener) maps the events the pack can't depend on —thallo-collectionsCollection*/CollectionRow*and contentEntry*events — into the recorder (the audit-listener pattern). - Read API —
GET /v1/admin/analytics/series(zero-filled daily time-series for a metric, optionally by subject),GET /v1/admin/analytics/summary(KPI totals + distinct active users over a range), andGET /v1/admin/analytics/breakdown(top subjects for one event over a range), behindauth+content_permission:analytics.read. - Retention —
php glueful analytics:prunedeletes rawanalytics_factspastanalytics.retention_days(default 90); the rollups and the distinct-actor table are never pruned.
The capability
The provider registers a single capability in boot():
new Capability('thallo.analytics', label: 'Analytics', description: '…');
- Enabled by default. An operator turns it off or on in the admin under Extensions ›
Capabilities. The switch is stored system-wide and overrides the deploy-time
thallo.capabilitiesconfig map. - Gated. When disabled, the read API routes are never registered (
404) and the pack's auth listeners do not subscribe. The core bridge for content and collection events is always wired and checks the same switch on every event, so it stops recording as soon as Analytics is off. Migrations run on install (not enable), so disabling preserves the tables. - Permission. The pack declares
analytics.read; the host app grants it toadministratorin its own dependent migration.
Privacy
The forever-kept analytics_active_actors table holds no identity — only a per-instance one-way
HMAC (actor_id_hash = hmac_sha256(actor_id, ANALYTICS_HASH_KEY | APP_KEY)), which preserves
uniqueness for counting without being reversible to a user. Raw actor_id exists only in
analytics_facts and is removed at the retention prune. Auth facts never carry token material, and
failed logins record no attempted username.
Boundary
Depends on glueful/thallo-contracts and glueful/framework — and never on glueful/thallo (the
application), the audit extension, or glueful/thallo-collections. The collection/content event bridge
lives in core (core/src/Analytics/) precisely so the pack stays dependency-pure; the repo's
composer boundaries check enforces this (no Thallo\Core\ references in src/ or routes/).
Install
The pack ships with Thallo: glueful/thallo-core requires it at the same version and the project's
config/serviceproviders.php loads its provider, so there is nothing to install or enable per pack.
Its tables are created by php glueful migrate:run with the rest of the schema.
Optionally set ANALYTICS_HASH_KEY (falls back to APP_KEY) and ANALYTICS_RETENTION_DAYS.
Turning the capability off (Extensions › Capabilities) removes the read API and the auth listeners. The analytics tables stay on disk.
Admin
The admin's Analytics page (admin/src/pages/analytics) charts this data through the read API.
HTTP/ops metrics stay with the framework metrics capability.
Contributing
This repository is a read-only mirror, published from
glueful/thallo on every release; its main is overwritten
by the next split, so nothing can land here. Issues and pull requests belong in glueful/thallo,
where this code lives at packages/thallo-analytics/.