"""Entry-point convention table — DATA, not logic. SoT: docs/specs/2026-06-17-attack-surface-mapping-design.md.
KIND_SIGNALS = the independent DENOMINATOR (permissive superset of what counts as an entry point, grep repo-wide).
CONVENTIONS  = the SUBSET the v1 mapper discovers (path hint + the same signal). A kind in KIND_SIGNALS with no
CONVENTIONS row is a named blind class -> reported `not-enumerated:kind:<name>`, never silently dropped.
A substring grep is NOT a signal (a naive webhook|queue|cron grep on multideal matched country-codes.ts / an i18n
store). Each signal is a precise call/decorator/export pattern; mark imprecise ones reliable=False.

Loaded by path (importlib.util.spec_from_file_location) per the project convention — NOT a package import.
This module imports no project code, so it loads identically under pytest, bench.py, and mapper.py."""
import re

# kind -> (precise signal regex, reliable). reliable=False => counted but flagged kind-signal:unreliable.
KIND_SIGNALS = {
    "http-defn-call":    (re.compile(r"\bdefineApi\s*\(|\bcreateRoute\s*\(|\bdefineEventHandler\s*\("), True),
    "http-file-route":   (re.compile(r"^export\s+(?:const|async\s+function)\s+(?:GET|POST|PUT|PATCH|DELETE)\b", re.M), True),
    "edge-function":     (re.compile(r"\bonRequest[A-Za-z]*\s*[=(]"), True),
    "webhook-receiver":  (re.compile(r"verif(?:y|ied)Signature|constructEvent|x-hub-signature|stripe-signature", re.I), True),
    # the CONSUMER handler `async queue(batch, env)` — NOT a producer `.queue(...).send()` (that is a sink, not an
    # entry point). Match the handler idiom precisely so producers don't false-positive.
    "queue-consumer":    (re.compile(r"\basync\s+queue\s*\(|\bqueue\s*\([^)]*\bbatch\b"), True),
    "cron-scheduled":    (re.compile(r"\basync\s+scheduled\s*\(|\bscheduled\s*\([^)]*\b(?:event|controller)\b"), True),
    # declared, v1 has NO discovery row -> these surface as not-enumerated:kind:* until a CONVENTIONS row is added
    "graphql-resolver":  (re.compile(r"@Resolver\b|createResolver\s*\(|Query\s*:\s*\{"), False),
    "auth-middleware":   (re.compile(r"\buse(?:Auth|Guard)\b|requireAuth\s*\(|authMiddleware\b"), False),
    "server-action-rpc": (re.compile(r"['\"]use server['\"]|createServerFn\s*\("), False),
    "cli-command":       (re.compile(r"\.command\s*\(|defineCommand\s*\("), False),
}

# the SUBSET the v1 mapper discovers. path_globs is a HINT for priority + reporting, NOT a gate: a defn-signal
# hit OUTSIDE these globs is still enumerated (the signal is primary). kind must key into KIND_SIGNALS.
CONVENTIONS = [
    {"kind": "http-defn-call",  "path_globs": ["**/server/api/**", "**/api/**"]},
    {"kind": "http-file-route", "path_globs": ["**/pages/api/**", "**/routes/**"]},
    {"kind": "edge-function",   "path_globs": ["**/functions/**"]},
]

# FILE-ROUTED kinds: a route is a FILESYSTEM fact (file LOCATION), independent of file CONTENT. This is the only
# GENUINELY INDEPENDENT within-kind denominator: actual = files matching the route-file rule; the content signal =
# what `enumerate` detects; a route file whose registration idiom the signal does NOT cover is then a VISIBLE recall
# miss (the cross-kind grep denominator CANNOT show this — it shares the signal). Call-registered kinds (defineApi)
# have NO filesystem oracle -> grep is best-available truth -> their per-kind recall is ~1.0 BY CONSTRUCTION, not
# validation; they are deliberately ABSENT here.
# Matched by PATH SEGMENTS, not fnmatch globs: fnmatch `*` crosses `/`, so `**/pages/api/**/*.ts` silently drops
# direct-child endpoints (`pages/api/foo.ts`) — a denominator undercount = false coverage. A rule is
# (segments, basename_rx_or_None): the segments must appear CONSECUTIVELY in the rel path with a file after them, and
# (if basename_rx is set) the file's basename must match. EXCLUDE_NONROUTE drops framework non-route files
# (Next/Astro `_`-prefixed private files, tests) so the denominator is routes, not helpers.
EXCLUDE_NONROUTE = re.compile(r"(^|/)_|\.(test|spec)\.[tj]sx?$")
FILE_ROUTE_RULES = {
    "http-file-route": [
        # Astro/Next src-layout: EVERY .ts/.js under src/pages/ is a route/endpoint, NOT just src/pages/api/.
        # Anchored on the CONSECUTIVE src+pages pair: `components/pages/` (no src) and `src/foo/pages/` do not match,
        # so the denominator stays routes. Astro sitemap/robots/[...] endpoints live directly under src/pages/ (not
        # api/) -> the old ("pages","api") rule false-dropped 6 real multideal routes (pilot STOP gate, #40 Part A).
        # Root-layout `pages/` with no src/ (older Next) is a deferred best-effort case (see spec NOT-covered).
        (("src", "pages"), None),
        (("app",), re.compile(r"^route\.[tj]sx?$")),    # Next app-router: app/**/route.ts
    ],
    "edge-function": [
        (("functions",), None),                         # CF Pages: every file under functions/ is a file-based route
    ],
}

# FALLBACK ONLY (non-git trees). In a git work tree mapper._walk_ts drives off `git ls-files` so `.gitignore` is the
# single source of truth (excludes node_modules, build output, nested worktrees/submodules in one stroke — a hardcoded
# list ALWAYS drifts: it missed `.opencode/worktrees` AND gitignored `tmp/`+`.dist-stack/`). This name list + the
# nested-`.git`-marker prune are the BEST-EFFORT backstop when the tree is not under git. A double-counted/build entry
# inflates coverage (a no-false-coverage violation), so keep common copy/build dirs here for that fallback path.
PRUNE_DIRS = ("node_modules", ".git", ".claude", ".opencode", ".worktrees", "dist", "build", ".next", ".astro")
