Skip to content

Contributor Metrics 2/3: aggregate-only collector and operations boundary #346

Description

@NotASithLord

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/contributions
Content-Type: application/json
Credentials: omitted
Redirects: 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.

Acceptance criteria

  • The deployed endpoint implements exactly the Contributor Metrics 1/3: closed local schema, consent, preview, and feedback #345 schema and the fixed receipt response.
  • Raw envelopes are aggregated transactionally and not retained.
  • No stable or cross-batch identifier exists.
  • Application storage/logs contain no IP, headers, user-agent, Origin, request body, or rejected value.
  • Retry deduplication works under concurrency and expires.
  • Aggregate scorecards can compare web actor code/tools efficiency, fallbacks, failures, and binary feedback by release/model cohort.
  • Abuse limits do not introduce identity machinery.
  • Operations, retention, logging, backup, kill-switch, and incident procedures are documented.
  • The companion repository PR and production/staging deployment are linked here.
  • Security/privacy/abuse review has no unresolved material finding.
  • No extension code or store/privacy claim changes in this issue.

Non-goals

  • Accounts, authentication, billing, model proxying, remote config, experimentation assignment, or user sync.
  • Raw event/session storage, debug artifact upload, traces, transcripts, browsing data, or free text.
  • Public fine-grained query APIs.
  • Perfect resistance to malicious metric poisoning.
  • Enabling or modifying the extension uploader.
  • Store telemetry enablement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions