""" 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")