"""Vocabulary for the internal system registry.""" from __future__ import annotations # ── Lifecycle ─────────────────────────────────────────────────────── STATUS_ACTIVE = "active" """在用 —— running and someone is responsible for it.""" STATUS_DEPRECATED = "deprecated" """待下线 —— still up, being wound down. Still needs an owner: a system nobody owns is exactly the one that stays up forever.""" STATUS_RETIRED = "retired" """已下线 —— gone. Kept as a row so historical bounty reports still resolve to a name, and excluded from the ownerless / attack-surface reads.""" STATUSES: tuple[str, ...] = (STATUS_ACTIVE, STATUS_DEPRECATED, STATUS_RETIRED) #: Statuses that still demand a named owner and still count as surface. LIVE_STATUSES: frozenset[str] = frozenset({STATUS_ACTIVE, STATUS_DEPRECATED}) # ── Exposure ──────────────────────────────────────────────────────── # # The axis the bounty programme cares about: an unauthenticated public # endpoint is a different risk from an internal-only tool, and the two # should never be averaged in a coverage number. EXPOSURE_PUBLIC = "public" """对外可达,无需认证即可触达(至少部分路径)。""" EXPOSURE_AUTHENTICATED = "authenticated" """对外可达但需要登录。""" EXPOSURE_INTERNAL = "internal" """仅内网 / VPN 可达。""" EXPOSURE_UNKNOWN = "unknown" """还没核实过 —— 明确区别于「已确认是内网」,否则未核实的系统会被当成安全的。""" EXPOSURES: tuple[str, ...] = ( EXPOSURE_PUBLIC, EXPOSURE_AUTHENTICATED, EXPOSURE_INTERNAL, EXPOSURE_UNKNOWN, ) #: What the bounty programme's attack surface actually is. EXTERNAL_EXPOSURES: frozenset[str] = frozenset({ EXPOSURE_PUBLIC, EXPOSURE_AUTHENTICATED, }) # ── Kind ──────────────────────────────────────────────────────────── KIND_PRODUCT = "product" """产品面 —— part of what customers buy (tidbcloud.com …).""" KIND_INTERNAL_TOOL = "internal_tool" """内部工具 —— built for staff (Helix, jira-crm …).""" KIND_MARKETING = "marketing" """市场/官网 —— www, blog, docs.""" KIND_DEMO = "demo" """演示/实验 —— event apps, demo deployments (TiCard, one-engine). Called out separately because "it's just a demo" is the most common reason a real vulnerability gets under-rated, and the registry should make that judgement explicit rather than implicit.""" KIND_SUPPORT = "support" """客户支持面 —— Clinic and friends.""" KIND_OTHER = "other" KINDS: tuple[str, ...] = ( KIND_PRODUCT, KIND_INTERNAL_TOOL, KIND_MARKETING, KIND_DEMO, KIND_SUPPORT, KIND_OTHER, ) # ── Bounds ────────────────────────────────────────────────────────── MAX_NAME_CHARS = 128 MAX_HOSTS = 20 MAX_NOTES_CHARS = 8_000 # ── Budget tracks ─────────────────────────────────────────────────── # # Mirrors the bounty domain's two pots. Duplicated as literals rather # than imported: domain modules never import each other (see CLAUDE.md # architecture rule 3), and the wire values are asserted equal by a test # so the two can never drift silently. TRACK_PRODUCT = "product" TRACK_IT = "it" TRACKS: tuple[str, ...] = (TRACK_PRODUCT, TRACK_IT) # ── Event types ───────────────────────────────────────────────────── EVENT_CREATED = "created" EVENT_UPDATED = "updated" EVENT_STATUS_CHANGED = "status_changed" EVENT_OWNER_ASSIGNED = "owner_assigned" EVENT_OWNER_CLEARED = "owner_cleared" EVENT_RETIRED = "retired" EVENT_OWNER_DISPUTED = "owner_disputed" """The named owner says the system is not theirs. Written by the self-service surface, never by IT — it records a claim to adjudicate, not a decision. Distinct from every other event here because it is the only one an ordinary employee can cause.""" EVENT_HANDOVER = "handover" """Ownership moved because the previous owner is leaving — written by the offboarding handover, not by a human editing the row. Distinct from ``owner_assigned`` so "was this a considered transfer or a leaver sweep?" stays answerable.""" EVENT_TYPES: tuple[str, ...] = ( EVENT_CREATED, EVENT_UPDATED, EVENT_STATUS_CHANGED, EVENT_OWNER_ASSIGNED, EVENT_OWNER_CLEARED, EVENT_RETIRED, EVENT_HANDOVER, EVENT_OWNER_DISPUTED, )