You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Parent: #344
Depends on the frozen v0 envelope from #345.
Outcome
Deploy a tiny aggregate-only contribution collector for Contributor Metrics.
The collector accepts one closed v0 envelope, validates it independently of the extension, transactionally rolls accepted counters into aggregate scorecard rows, and discards the request body. It is not a peerd application backend, account system, telemetry vendor, raw-event warehouse, or configuration service.
The implementation is expected to live with peerd's web/deployment infrastructure (currently the separate NotASithLord/peerd-site repository), but this issue remains the product/security acceptance authority and must link the companion implementation PR/deployment.
API contract
Request
A single versioned endpoint, conceptually:
POST /v1/contributionsContent-Type: application/jsonCredentials: omittedRedirects: refused
The body is exactly the canonical envelope defined in #345 plus a single-use batch idempotency token added by the uploader:
The token is random per sealed envelope.
It is never reused across batches and cannot identify an installation.
The collector retains it only for a bounded retry/deduplication window.
It is never copied into aggregate scorecard rows.
The server owns an independent schema implementation. It does not trust that the extension already validated the body.
Validation
Fail the whole request—never partially accept—when:
The schema version is unknown.
Required keys are missing or unknown keys are present.
A string is outside its exact enum/registry format.
A custom/unknown model value is anything other than the literal custom.
A counter is negative, non-integral, non-finite, or above its plausibility cap.
A histogram has unknown buckets or an inconsistent total.
The row/envelope count or encoded byte size exceeds named server constants.
JSON has duplicate keys, unsafe numeric coercions, or a non-object top level.
Content-like fields, arbitrary properties, identifiers, or unbounded strings appear anywhere.
No "best effort" field dropping: a mismatched client/server contract should be visible as a version error, not silently widen collection.
Response
Return a small fixed receipt only, for example an accepted/rejected status and supported schema number.
The endpoint must not return:
Configuration, feature flags, sampling values, experiment arms, prompts, model advice, URLs, code, messages, or arbitrary text.
A stable client token or cookie.
Any value consumed by peerd except upload bookkeeping.
Disable redirects and cookies at the edge as well as in the extension client.
Aggregate data model
Persist only what is required to answer release/feature questions:
Coarse server receipt period.
Extension version/channel/browser family.
Known provider/model family or custom.
Feature/variant/resolved variant/fallback enums.
Summed counters and histogram buckets.
User-reported binary feedback totals.
Server-side rejection/health counts that contain no submitted body values.
Do not persist:
The raw envelope or request body.
Headers, full user-agent, Origin, Referer, IP address, geographic derivation, locale, TLS fingerprint, cookie, batch body hash, or request log containing those values.
Batch ids beyond the bounded dedupe window.
One row per request when it could permit reconstruction of a single contributor's envelope.
Debug logs containing rejected JSON or validation values.
Aggregation must be transactional: either all rows/counters in one valid envelope are incorporated once, or none are.
Scorecards must state that the dataset is opt-in, observational, and untrusted. It cannot establish population prevalence or causal superiority by itself.
Privacy/retention posture
Raw request body lifetime ends with the validation/aggregation transaction.
Application request logging is disabled or strictly metadata-free.
Infrastructure/edge processing and any unavoidable transient IP handling are documented honestly in the published privacy/operations note.
Deduplication tokens expire under a named code-owned retention constant.
Aggregate retention is documented and bounded; changing it requires review.
Backups contain aggregate tables only, never raw envelopes or access logs.
There is no user deletion endpoint because no user identity/correlation key exists; this limitation is explained before extension consent.
No third-party analytics, telemetry SDK, session replay, or log-drain product receives requests.
A deployment is not accepted on a platform where request bodies/IPs cannot be prevented from entering retained logs without an explicit owner decision and disclosure update.
Abuse and poisoning
The endpoint is intentionally anonymous, so uploaded metrics are not ground truth.
Use:
Transport-level and body-size caps before JSON parsing.
Coarse edge rate limiting. An IP may be used transiently to enforce the limit but must not be written into the application data model or logs.
Exact allowed extension origins/CORS where useful, while recognizing Origin is not authentication.
Per-envelope plausibility caps and server-side rejection metrics.
A short-lived batch-id dedupe table for safe retries.
Operational alerts on volume/schema rejection changes.
Do not introduce a stable installation credential, fingerprint, proof-of-work, CAPTCHA, account, or remote attestation merely to make an anonymous metric perfect. Treat poisoning as a statistical/operations limitation.
Deployment boundary
The collector is a one-way sink:
It cannot call into the extension, dweb, providers, or user browsers.
It exposes no public query API for raw or fine-grained rows.
Dashboard/analysis access is read-only over aggregates and separately authenticated.
The extension's endpoint is pinned to one production origin.
Staging uses a distinct non-production origin/build path and cannot receive production contributions accidentally.
Secrets and deployment configuration live in the deployment platform, not the extension repository.
No remotely hosted executable code is served to the extension.
Document incident response:
Kill switch at the endpoint.
How to stop ingestion without pushing an extension update.
How to verify raw logging remains disabled.
How to rotate deployment credentials.
What aggregate state is recoverable/backed up.
How a schema version is retired.
The kill switch may reject uploads; it may not remotely change extension behavior.
Tests
Pure/schema
Golden extension envelope fixtures are accepted byte-for-byte.
Unknown versions/keys/enums/buckets and malformed JSON are rejected.
Arbitrary/content-like strings and custom model names cannot pass.
Counter, histogram, row, nesting, and encoded-size boundaries fail closed.
Duplicate JSON keys and numeric overflow/coercion cases are rejected.
Valid aggregation is deterministic and associative.
Integration
One valid request updates only aggregate counters.
A repeated batch id within the dedupe window is acknowledged without double-counting.
The same payload under a new batch id is treated as a new anonymous contribution; there is no cross-batch identity.
Invalid multi-row envelopes update nothing.
Concurrent duplicate delivery increments once.
Transaction/DB failure leaves no partial aggregate.
Response contains only the fixed receipt contract and sets no cookie.
Redirect, alternate method/path, wrong content type, and oversized body are refused.
CORS/Origin posture matches packaged extension ids but is not treated as authentication.
Privacy/operations
Tests or deployment assertions prove request-body logging is disabled.
Stored aggregate rows contain no raw envelope, batch id, IP, header, exact timestamp, or arbitrary string.
Dedupe records expire.
Backups/export jobs target aggregate tables only.
A red-team corpus containing URLs, prompts, raw errors, selectors, IDs, and encoded content is rejected without logging the submitted values.
Load/rate tests demonstrate bounded work before/after parsing.
Parent: #344
Depends on the frozen v0 envelope from #345.
Outcome
Deploy a tiny aggregate-only contribution collector for Contributor Metrics.
The collector accepts one closed v0 envelope, validates it independently of the extension, transactionally rolls accepted counters into aggregate scorecard rows, and discards the request body. It is not a peerd application backend, account system, telemetry vendor, raw-event warehouse, or configuration service.
The implementation is expected to live with peerd's web/deployment infrastructure (currently the separate
NotASithLord/peerd-siterepository), but this issue remains the product/security acceptance authority and must link the companion implementation PR/deployment.API contract
Request
A single versioned endpoint, conceptually:
The body is exactly the canonical envelope defined in #345 plus a single-use batch idempotency token added by the uploader:
The server owns an independent schema implementation. It does not trust that the extension already validated the body.
Validation
Fail the whole request—never partially accept—when:
custom.No "best effort" field dropping: a mismatched client/server contract should be visible as a version error, not silently widen collection.
Response
Return a small fixed receipt only, for example an accepted/rejected status and supported schema number.
The endpoint must not return:
Disable redirects and cookies at the edge as well as in the extension client.
Aggregate data model
Persist only what is required to answer release/feature questions:
custom.Do not persist:
Aggregation must be transactional: either all rows/counters in one valid envelope are incorporated once, or none are.
Scorecards must state that the dataset is opt-in, observational, and untrusted. It cannot establish population prevalence or causal superiority by itself.
Privacy/retention posture
A deployment is not accepted on a platform where request bodies/IPs cannot be prevented from entering retained logs without an explicit owner decision and disclosure update.
Abuse and poisoning
The endpoint is intentionally anonymous, so uploaded metrics are not ground truth.
Use:
Do not introduce a stable installation credential, fingerprint, proof-of-work, CAPTCHA, account, or remote attestation merely to make an anonymous metric perfect. Treat poisoning as a statistical/operations limitation.
Deployment boundary
The collector is a one-way sink:
Document incident response:
The kill switch may reject uploads; it may not remotely change extension behavior.
Tests
Pure/schema
Integration
Privacy/operations
Acceptance criteria
Non-goals