diff --git a/AGENTS.md b/AGENTS.md index 4033c37..90d00b4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ For every Go-related task, always invoke and follow the `golang-how-to` skill, t - The backend is the authority for authentication, authorization, tenant isolation, metric definitions, and data provenance. - Every merchant-scoped query derives merchant identity from the authenticated server-side session; never trust a client-supplied merchant identifier as authorization. -- Cross-merchant results are minimum-cohort, anonymized aggregates only. +- Cross-merchant results are anonymized aggregates only. Exclude the active merchant and suppress the benchmark unless at least 10 other merchants and 1,000 combined peer sessions contribute to the approved cohort/window. - Treat `payer_card_key` as an opaque, merchant-scoped source token with unverified upstream semantics. Retain it unchanged only for internal deterministic analytics; never expose it through merchant APIs, logs, exports, or AI inputs, and never describe it as a card number or verified person identifier. - Use integer minor currency units or an explicitly documented fixed-precision representation; never floating point for money. - Interpret timezone-naive challenge timestamps as `Asia/Tehran`, preserve their source provenance, and normalize canonical database/API timestamps to UTC `timestamptz`/ISO 8601. Any change to this assumption requires a new import version and ADR. diff --git a/docs/DATA-FOUNDATION-SPEC.md b/docs/DATA-FOUNDATION-SPEC.md index 6564521..45b016c 100644 --- a/docs/DATA-FOUNDATION-SPEC.md +++ b/docs/DATA-FOUNDATION-SPEC.md @@ -156,7 +156,8 @@ Resolved: interpret timezone-naive source timestamps as `Asia/Tehran`, normalize Resolved: retain `payer_card_key` unchanged as a restricted, merchant-scoped opaque source token for internal repeat-payer analytics. Do not HMAC it, expose it, log it, export it, or send it to AI providers. Its upstream semantics remain unverified. See ADR 0005. +Resolved: peer benchmarks exclude the active merchant and require at least 10 other merchants plus 1,000 combined peer sessions in the approved cohort/window. Otherwise the benchmark is suppressed as insufficient data; the merchant's own analytics remain available. See ADR 0006. + Remaining approval gates: -1. Minimum cohort size and suppression policy for peer benchmarks. -2. Physical partitioning/materialization strategy, which must follow measured query needs rather than the logical ERD alone. +1. Physical partitioning/materialization strategy, which must follow measured query needs rather than the logical ERD alone. diff --git a/docs/decisions/0006-peer-benchmark-suppression.md b/docs/decisions/0006-peer-benchmark-suppression.md new file mode 100644 index 0000000..697125e --- /dev/null +++ b/docs/decisions/0006-peer-benchmark-suppression.md @@ -0,0 +1,28 @@ +# ADR 0006: Suppress under-sized peer benchmarks + +- Status: Accepted +- Date: 2026-08-20 + +## Context + +The product may compare a merchant's metrics with anonymized merchants in the same approved cohort. Comparisons built from very few businesses can reveal competitive information, and comparisons built from too few payment sessions are unstable. The five source categories contain 25, 38, 53, 69, and 158 merchants over the full dataset, so a useful minimum can be enforced without disabling category benchmarks globally. + +## Decision + +Exclude the active merchant from every peer aggregate. Display a peer benchmark only when its selected cohort and reporting window contain both: + +- at least 10 other eligible merchants; and +- at least 1,000 combined peer payment sessions. + +If either condition fails, return a typed suppressed result rather than a numeric value. The UI explains that there is not enough data for a reliable comparison. Suppression applies only to peer comparisons; authenticated merchants retain access to their own permitted analytics. + +Peer cohorts and reporting windows come from an approved allowlist. Merchant-facing clients cannot construct arbitrary cross-merchant filters that could isolate a competitor or recover values by differencing nearby queries. Individual peer identities, values, rows, and ranks are never returned. + +## Consequences + +- Benchmarks are less likely to expose a small set of competitors or amplify tiny samples. +- Narrow date windows may suppress otherwise valid category comparisons; the UI may suggest a broader approved period. +- API contracts distinguish available, suppressed, and unavailable/error states. +- Tests cover both thresholds, exclusion of the active merchant, fixed cohort definitions, and attempts to vary filters around the threshold. +- The thresholds are product policy, not a formal anonymity guarantee; changing them requires a reviewed ADR and security/privacy assessment. +