242 lines
14 KiB
Markdown
242 lines
14 KiB
Markdown
# Optional product usage and feedback (#1110)
|
|
|
|
Current scope: **usage.v4**. v4 replaces `gallery_downloads` with
|
|
`gallery_downloads_restricted` after explicit `usage-consent.v4` consent.
|
|
All v1/v2/v3 schemas and queued packets remain immutable. Historical views
|
|
keep both questions separate; neither can be inferred by inverting the other.
|
|
|
|
The inventory and other capabilities are unchanged from v3. The expanded catalog contains 86 capabilities
|
|
(including ML face recognition and invoice import) and exactly two inventory
|
|
totals: stored gallery records and non-video photo records, including drafts
|
|
and retained archive records. No content, identifiers, per-gallery breakdowns,
|
|
biometric results, financial values or visitor actions.
|
|
|
|
Existing v1/v2/v3 participants retain their previous scope until explicit signed
|
|
v4 consent is confirmed. The new restriction query does not run before that
|
|
confirmation. Collector must be deployed first. v1/v2/v3 wire schemas and raw
|
|
history remain unchanged. See [current coverage](FEATURE_COVERAGE.md) for all
|
|
definitions and [v4 inventory](usage-coverage.v4.json) for code boundaries.
|
|
|
|
The sections below also document the historical v1/v2 implementation. Any
|
|
statements excluding all gallery/photo counts describe those earlier versions;
|
|
v3 adds only the two installation totals above.
|
|
|
|
Backward compatibility is required for future changes. The collector continues
|
|
to accept v1/v2/v3/v4 reports, including omitted or null measurements, using their
|
|
declared schema and original reporting day. Missing values remain unknown in
|
|
aggregates and histories. PicPeak still emits complete reports through the
|
|
unchanged sender schemas; only reception is more tolerant. Consent, field
|
|
allowlists, signatures and retry/deletion rules remain mandatory. Deploy the
|
|
compatible collector first; old PicPeak clients require no update or renewed
|
|
consent to keep delivering their existing report scope. New fields still
|
|
require a new explicitly consented schema; never redefine an old field.
|
|
|
|
|
|
Tracking is disabled by default. After updating, settings editors see a
|
|
dismissible invitation in the admin shell. Only explicit consent in Settings →
|
|
Product usage & feedback registers an installation. Public galleries never
|
|
load the usage UI chunk or trigger product reports.
|
|
|
|
The backend builds and signs allowlisted feature packets, sending them at most
|
|
daily on authenticated admin use. It does not send gallery visitors, click
|
|
histories, photo/gallery counts, names, emails, domains, filenames, or secrets.
|
|
The settings page provides disclosure, raw preview/export, a private lookup
|
|
hash, delivery status, feedback, and a short-lived voting connection.
|
|
|
|
## Deployment
|
|
|
|
Run the normal core migrations; migration 201 creates dedicated state/marker
|
|
tables. No identity or key is created by the migration. No extra browser
|
|
tracker or CORS policy is required. The collector is the separate
|
|
`picpeak-usage` app from [#1110](https://github.com/PicPeak/picpeak/issues/1110).
|
|
|
|
| Backend variable | Default | Meaning |
|
|
| --- | --- | --- |
|
|
| `USAGE_COLLECTOR_URL` | https://usage.picpeak.app | Fixed operator-configured collector origin, HTTPS in production |
|
|
| `USAGE_ENCRYPTION_KEY` | JWT_SECRET | 32+ characters, encrypts the local Ed25519 key with AES-256-GCM |
|
|
|
|
Both database engines are supported. The state and marker tables are created
|
|
by migrations 201-205 on PostgreSQL and SQLite alike, and the engine-sensitive
|
|
paths are covered by `__tests__/integration/productUsagePg.test.js` against a
|
|
real PostgreSQL — bigint columns come back as strings there, booleans are real
|
|
booleans rather than 0/1, and the marker write takes `SELECT ... FOR UPDATE`
|
|
only on that engine. That suite is gated behind `PICPEAK_PG_TEST_URL` and runs
|
|
in CI, which provides one.
|
|
|
|
If `USAGE_COLLECTOR_URL` is unset, empty or blank the built-in default
|
|
`https://usage.picpeak.app` is used. A value that is present but malformed is
|
|
reported as a configuration error rather than being replaced by the default:
|
|
silently retargeting a self-hosted collector at ours would send reports
|
|
somewhere the operator did not choose.
|
|
|
|
Local development can use an HTTP loopback collector outside production. The
|
|
collector URL is never writable through generic settings or request payloads.
|
|
|
|
### The connection only runs outwards
|
|
|
|
PicPeak sends; it never pulls. There is exactly one place in the service that
|
|
reaches the network, it is a POST, and it makes requests to exactly two paths:
|
|
`/api/envelopes` and — only when an operator asks for their own data export —
|
|
`/api/participant/lookup`. There is no scheduled job that contacts the
|
|
collector (the daily rollup is driven solely by an authenticated admin hitting
|
|
`/activity`), no route the collector could call, and `redirect: 'error'` so the
|
|
collector cannot even redirect a request elsewhere.
|
|
|
|
From a reply the service reads only the acknowledgement for the packet it just
|
|
sent, and compares `packet_id`, `installation_id`, `packet_digest`, `action`,
|
|
`sequence` and `status` against that packet before accepting it; a mismatch is
|
|
an error and nothing else in the response is looked at. The stored copy drops
|
|
the session token, and no read path hands it back to the UI. A requested data
|
|
export is streamed to the operator as a file attachment and is never
|
|
interpreted or executed.
|
|
|
|
The consequence is the point, and it is stated in the consent dialog: this
|
|
channel cannot deliver code, configuration or content into an installation —
|
|
not even from a collector that has been taken over. It is a one-way path by
|
|
design, not by convention, and `__tests__/services/usageOutboundOnly.test.js`
|
|
fails if that ever stops being true.
|
|
Keep the encryption material stable and protected; losing it makes the old
|
|
identity unable to sign deletion requests. Note that `USAGE_ENCRYPTION_KEY`
|
|
defaults to `JWT_SECRET`, so rotating `JWT_SECRET` without setting a dedicated
|
|
`USAGE_ENCRYPTION_KEY` first loses it. The settings page then reports
|
|
`SIGNING_KEY_UNREADABLE` rather than a generic delivery failure, because the
|
|
consequence is specific: reports stop and the deletion request can no longer
|
|
be signed either. Restoring the original key material is the correct fix and
|
|
completes the pending deletion. When it is genuinely gone — a rotation done
|
|
because the secret was compromised — the settings page offers **Discard local
|
|
identity** (`POST /api/admin/usage/abandon`), which is available in no other
|
|
state. It erases the local identity, key material and markers and records an
|
|
abandonment receipt marked `collector-unconfirmed`: the collector was never
|
|
told, so it keeps the reports already accepted, and the receipt says so rather
|
|
than claiming a deletion that did not happen. Participation can be started
|
|
again afterwards with a fresh identity.
|
|
|
|
The same exit covers the other way a participation can become impossible to
|
|
finish: a collector that rejects the packet outright. Opting in to usage.v2
|
|
against a collector that still only speaks usage.v1 — the deployment order
|
|
this document warns about above — is answered with `INVALID_PACKET`, which is
|
|
surfaced as `SCHEMA_NOT_ACCEPTED` rather than a generic delivery failure,
|
|
because retrying cannot resolve it. Nothing is registered in that case, so
|
|
**Discard local identity** is offered immediately and its receipt records
|
|
`never-registered` rather than an unconfirmed deletion. The exit is never
|
|
offered while a participation the collector *did* accept could still be
|
|
deleted remotely; that case keeps the explicit warning.
|
|
|
|
Keys live in a dedicated database
|
|
table, not the generic readable settings. A random mode-0600 file at
|
|
`getStoragePath()/usage-instance.key` binds the database to its local storage.
|
|
|
|
## Consent and deletion lifecycle
|
|
|
|
### Versioned, explicit scope upgrades
|
|
|
|
New participants explicitly consent to usage.v2. Existing v1 participants stay
|
|
on v1 until they review and explicitly accept the expanded scope; migration 205
|
|
defaults their consent to v1. A signed consent command preserves the identity
|
|
and raw history. Collector confirmation atomically upgrades local consent and
|
|
resets the local used-marker observation period. Lost receipts/outages leave the
|
|
upgrade visibly pending and retryable, with v1-only collection until confirmed.
|
|
Opt-out still stops everything immediately. Deploy the v2 collector first.
|
|
|
|
The [complete feature and privacy matrix](FEATURE_COVERAGE.md) lists all 73
|
|
signals (19 existing, 54 new), all 81 current route families and 26 feature flags.
|
|
56 capabilities have configured/used booleans; 17 guest-facing or automatic
|
|
capabilities are configuration-only, without a used field. The full catalog is
|
|
available locally before consent in EN/DE and publicly in the usage portal.
|
|
Missing signals from older versions are unknown in aggregates, not unused.
|
|
|
|
### Participation lifecycle
|
|
|
|
Disabled → activation pending → active. Registration/delivery failures are
|
|
durable and retried. Multiple admin tabs/processes share a database lease;
|
|
only accepted receipts advance the sequence and report date. Re-signed retries
|
|
reuse the immutable packet ID so lost acknowledgements do not duplicate data.
|
|
|
|
Retries are paced (migration 206). Consecutive failures set `attempts` and
|
|
`next_attempt_at`, and the unattended sender — the activity endpoint and the
|
|
settings ticker — waits for that gate: 2, 4, 8, 16, 32 minutes, then hourly.
|
|
Without it a packet the collector rejects permanently produced one collector
|
|
request per admin action, because any authenticated admin reaches the activity
|
|
endpoint and every open admin tab fires it every five minutes. Explicit
|
|
operator actions are not paced: **Retry** and opt-out send immediately, and the
|
|
settings page names the time of the next automatic attempt so a waiting
|
|
installation does not read as a broken one.
|
|
|
|
Opt-out immediately stops collection, clears markers/previews/feedback
|
|
preferences, and enters deletion pending. It keeps only credentials and the
|
|
deletion operation until the collector confirms deletion. The collector removes
|
|
reports, projections, feedback/publications, votes, and sessions. PicPeak then
|
|
erases the local fingerprint, private key and binding. A later join generates
|
|
a fresh identity. Repeated deletion handles lost receipts safely.
|
|
|
|
Migration 204 adds bounded, local-only privacy receipts and removes any legacy
|
|
plaintext voting token from the last collector receipt. A completed export
|
|
records its time, the number of accepted reports and the total number of
|
|
accepted packets separately — feedback, votes and portal sessions are
|
|
participant operations, not reports, and a receipt that folded them into one
|
|
"reports" figure stated something untrue about its own contents. Confirmed
|
|
opt-out replaces this with a deletion receipt containing only a random receipt
|
|
ID, time, status and fixed scope. It retains no old installation hash, key, payload or credential. The
|
|
settings page can download these receipts even after opt-out. They are local
|
|
records of the collector acknowledgement, not independent proof of storage
|
|
erasure. Downloaded exports carry their own dated receipt; the collector does
|
|
not create a permanent per-person access/export log.
|
|
|
|
A missing/mismatched storage binding or conflicting collector sequence stops
|
|
reporting with identity conflict. A full clone of a signing identity cannot be
|
|
distinguished cryptographically. Do not run the same participation identity in
|
|
two deployments; disable/delete the old participation and rejoin. Deletion
|
|
affects any other copy that shared the same identity.
|
|
|
|
## Feedback and permissions
|
|
|
|
Only settings.edit can inspect identity/packets or change participation and
|
|
feedback preferences. Any authenticated admin may trigger the fixed daily
|
|
report; the activity endpoint accepts no telemetry input. Every usage endpoint
|
|
uses adminAuth, including token-type checks. Gallery tokens cannot use it.
|
|
|
|
Feedback, votes and portal sessions share one installation-wide budget of 30
|
|
per hour. They are the only endpoints whose effect is an outbound request
|
|
carrying operator-written free text, and the platform's general limiter skips
|
|
authenticated requests by design — correct for endpoints that touch only this
|
|
installation, wrong for a relay. Reading status, retrying and opting out are
|
|
never throttled: those are how an operator sees what is happening and how they
|
|
leave.
|
|
|
|
Feedback is sent only on explicit submission. Each item defaults anonymous and
|
|
private; names, publication permission, and testimonial marketing permission
|
|
are separate choices. Published requests/testimonials require maintainer review.
|
|
Public voting uses a backend-authorized 15-minute session, never the lookup hash.
|
|
|
|
## Contract
|
|
|
|
The closed v1/v2/v3/v4 schemas are in `backend/src/usage/schema.cjs`, with signing in
|
|
`protocol.cjs`. Keep these and all versioned `features.v*.json` catalogs byte-identical to the collector's `protocol/` copies.
|
|
The collector serves its schema and complete source archive publicly. Aggregate
|
|
projections and the complete dataset are accessible to participating
|
|
installations only; raw reports require the installation's confidential lookup
|
|
hash. Raw exports contain the first accepted envelope of every unique usage
|
|
report. Re-signed transport retries are deduplicated; feedback, registration,
|
|
sessions and rejected requests are not usage reports. Full exports use a
|
|
consistent database snapshot at their start, not a 200-record total limit.
|
|
Feature semantics and retention are documented in its
|
|
`docs/PROTOCOL.md` and `docs/OPERATIONS.md`.
|
|
|
|
Public, reviewed testimonials are separate from marketing approval. Homepage
|
|
integrations must use `/api/public/marketing-testimonials`, never the general
|
|
portal testimonial feed. Each page is bounded and exposes its continuation
|
|
cursor. Deletion removes the source publication; operators must also remove
|
|
any externally copied content and follow the documented backup/log policies.
|
|
|
|
Used flags represent successful allowlisted admin capability calls since
|
|
consent to the current schema (v1: joining; v2/v3/v4: joining or explicit upgrade),
|
|
not visitor behavior or counts. OAuth marks successful admin SSO;
|
|
applied CSS is observed during report generation. Gallery layouts are controlled
|
|
enums extracted from event themes without IDs or counts. Other signals use the
|
|
explicit rules in `middleware/productUsage.js`, `usage/capabilityRules.js`,
|
|
`usage/capabilityEvidence.js`, `usage/expandedSnapshot.js` and `usage/UsageService.js`.
|
|
|
|
Tests: `backend/__tests__/routes/adminUsage.test.js`, frontend
|
|
`features/settings/__tests__/ProductUsageTab.test.tsx`, and the collector's
|
|
cross-repository integration suite with isolated databases and real HTTP.
|