receipter-pro / backend /core /constants.py
M8ion deploy
Deploy backend
45dc2a1
Raw History Blame Contribute Delete
8.45 kB
"""
Central registry for cross-cutting, iteration-prone values.
This is the ONE place values like these are defined β€” every other module imports from
here, never hand-copies a number or a list. See docs/blueprint/blueprint.md and
docs/product/checklist.md ("central intelligence... registry") for why this file exists:
these are exactly the values that change a lot across iterations, and a change here should
be a visible, deliberate moment, not a silent edit buried in an unrelated diff.
scripts/run_tests.py diffs this file against the last commit on every test run and prints
a notice (not a failure) listing what changed, so a change here is never silent.
Every value below marked "prototype placeholder" is not a business decision β€” it exists so
local development and tests have something concrete to run against, and is expected to be
replaced with a real, deliberately-chosen number before Phase 2.
"""
from __future__ import annotations
# ---------------------------------------------------------------------------
# Versioning
# ---------------------------------------------------------------------------
# Application version. Bump on every meaningful release (Phase 4 onward, this drives
# docs/general/CHANGELOG.md entries).
APP_VERSION = "0.1.0-proto"
# Schema version for the data model. Bump when a migration changes stored-data shape in a
# way that old rows won't automatically satisfy β€” not on every schema tweak.
SCHEMA_VERSION = "0.1.0"
# ---------------------------------------------------------------------------
# Org roles β€” see docs/blueprint/blueprint.md "Core entities"
# ---------------------------------------------------------------------------
# Ranked low to high. An action gated at a given role is allowed for that role and every
# role ranked above it (e.g. a check gated at "operator" also passes for "admin"/"owner").
ROLE_RANK: dict[str, int] = {
"viewer": 0,
"operator": 1,
"editor": 1,
"admin": 2,
"owner": 3,
}
# What a role can be allowed to do. Seeing the org's results is not on this list: every
# member can always see them. Labels are shown to owners when they set up a role.
PERMISSIONS: dict[str, str] = {
"edit_lists": "Add and change lists and sign-up forms",
"edit_emails": "Create and change saved emails and looks",
"send": "Send campaigns",
"manage_mailboxes": "Connect and manage mailboxes",
"manage_settings": "Change organisation settings",
"manage_team": "Invite people and change their roles",
}
# Roles every org starts with. Owner always has everything and can't be changed; the
# others are starting points an owner can edit. Custom roles sit beside these.
BUILTIN_ROLES: dict[str, dict] = {
"owner": {"name": "Owner", "permissions": list(PERMISSIONS)},
"admin": {"name": "Admin", "permissions": list(PERMISSIONS)},
"editor": {"name": "Editor", "permissions": ["edit_lists", "edit_emails", "send"]},
"viewer": {"name": "Viewer", "permissions": []},
}
# Older stored role names, read as their current equivalent.
ROLE_ALIASES: dict[str, str] = {"operator": "editor"}
# Days an invite link stays usable.
INVITE_TTL_DAYS = 7
# The role assigned automatically to whoever creates an org. Not assignable via
# add_member β€” ownership only exists by creating the org (transfer is out of scope for now).
ORG_CREATOR_ROLE = "owner"
# ---------------------------------------------------------------------------
# Mailbox connections β€” see docs/blueprint/adr/0001-relay-not-esp.md
# ---------------------------------------------------------------------------
# Providers that get the OAuth path. Everything else falls through to raw SMTP credentials.
SUPPORTED_OAUTH_PROVIDERS: tuple[str, ...] = ("gmail", "outlook", "yahoo")
# Days between changes to a connected mailbox's sign-in details (a change re-runs the test).
MAILBOX_CREDENTIAL_CHANGE_DAYS = 30
# Highest daily send limit an org can set on one mailbox. 0 means no limit of our own.
MAILBOX_DAILY_LIMIT_MAX = 10_000
# Number of OTP verification messages sent in the mailbox-connection round-trip
# (docs/product/srs.md FR-6).
VERIFICATION_OTP_COUNT = 3
# Seconds to wait for all VERIFICATION_OTP_COUNT messages to arrive before treating the
# connection as a silent failure (SRS FR-7). Prototype placeholder.
VERIFICATION_TIMEOUT_SECONDS = 300
# ---------------------------------------------------------------------------
# Rate limiting & abuse prevention β€” see docs/blueprint/blueprint.md "Rate limiting"
# ---------------------------------------------------------------------------
# Default hourly send cap per account, enforced server-side via a rolling token bucket.
# Prototype placeholder β€” real tiering happens in PLAN_TIERS below once pricing is set.
DEFAULT_HOURLY_SEND_CAP = 100
# Escalation stages for anomaly detection, in order. Each stage's threshold is a multiple
# of the account's own rolling baseline rate, not an absolute number.
RATE_ANOMALY_ESCALATION_STAGES: tuple[str, ...] = (
"detect",
"block",
"silently_report",
"auto_temporary_ban",
)
# ---------------------------------------------------------------------------
# Bounce handling β€” see docs/blueprint/blueprint.md "Sending pipeline"
# ---------------------------------------------------------------------------
# Soft-bounce retry ceiling before escalating to a person. Prototype placeholder.
SOFT_BOUNCE_RETRY_CEILING = 5
# Backoff schedule (seconds) between soft-bounce retries, up to the ceiling above.
SOFT_BOUNCE_BACKOFF_SECONDS: tuple[int, ...] = (60, 300, 900, 3600, 14400)
# Initial window (seconds) to match an inbound message to a specific sent OTP/message by
# direct reference before escalating to the broader regex rescan pass.
BOUNCE_MATCH_INITIAL_WINDOW_SECONDS = 120
# ---------------------------------------------------------------------------
# Billing β€” see docs/blueprint/adr/0005-per-recipient-pay-as-you-go-billing.md
# ---------------------------------------------------------------------------
# Soft-cap usage warning thresholds, as a fraction of an org's monthly free allowance.
# Fires on all 3 surfaces (email, in-app, control panel banner) per SRS FR-28.
SOFT_CAP_WARNING_THRESHOLDS: tuple[float, ...] = (0.8, 1.0)
# Plan tiers. Real pricing/allowances are a business decision (see ADR-0005) β€” these are
# structural placeholders so the billing code has real shapes to run tests against.
PLAN_TIERS: dict[str, dict[str, object]] = {
"free": {
"monthly_recipient_allowance": 500, # prototype placeholder
"hourly_send_cap": DEFAULT_HOURLY_SEND_CAP,
"storage_mb": 250, # prototype placeholder
"overage_allowed": False,
},
"paid": {
"monthly_recipient_allowance": None, # unlimited within pay-as-you-go
"hourly_send_cap": DEFAULT_HOURLY_SEND_CAP * 5, # prototype placeholder
"storage_mb": 5000, # prototype placeholder
"overage_allowed": True,
},
}
# ---------------------------------------------------------------------------
# M8ion's own outbound identity β€” see docs/blueprint/blueprint.md "M8ion's own outbound mail"
# ---------------------------------------------------------------------------
# Prototype-only sending address for M8ion's own transactional mail. Replaced with a real
# registered domain before Phase 3.
SYSTEM_SENDER_ADDRESS_PROTO = "projects.techies.26@gmail.com"
# The receiving side of the mailbox-verification round-trip (SRS FR-6) β€” "M8ion's own test
# mailbox". Prototype placeholder; becomes a real address once a domain is registered.
SYSTEM_TEST_MAILBOX_ADDRESS_PROTO = "verify-test@m8ion-test.local"
# ---------------------------------------------------------------------------
# Public collection endpoint (docs/blueprint/adr/0006) β€” all prototype placeholders
# ---------------------------------------------------------------------------
COLLECT_MAX_BODY_BYTES = 8_000
COLLECT_MAX_FIELDS = 30
COLLECT_MAX_VALUE_LENGTH = 500
COLLECT_MIN_FILL_SECONDS = 2 # a human cannot fill a form faster than this
COLLECT_PER_KEY_PER_MINUTE = 30
COLLECT_PER_IP_PER_MINUTE = 10
COLLECT_PER_ADDRESS_PER_HOUR = 1 # confirmation emails to one address, per key
COLLECT_DEFAULT_DAILY_CAP = 500
COLLECT_CONFIRM_TTL_HOURS = 48
# Field names that are never collected, whatever the key allows.
COLLECT_BLOCKED_FIELD_WORDS = ("password", "passwd", "pwd", "card", "cvv", "cvc", "ssn", "secret", "token")