[ { "id": "F001", "description": "Migration: create inbound_email_rules table with (tenant, id) composite PK, position/provider_ids/conditions/action_type/action_config/on_no_match/fallback_inbound_ticket_defaults_id columns, tenant+position index, Citus distribution, transaction:false", "implemented": true, "prdRefs": [ "Functional Requirements #1" ] }, { "id": "F002", "description": "Migration: create client_name_aliases table with (tenant, id) PK, client_id, alias, unique index on (tenant, lower(alias)), tenant+client index, Citus distribution", "implemented": true, "prdRefs": [ "Functional Requirements #1" ] }, { "id": "F003", "description": "Migration: extend email_processed_messages processing_status check constraint to include 'skipped'", "implemented": true, "prdRefs": [ "Functional Requirements #1" ] }, { "id": "F004", "description": "Shared TypeScript types for InboundEmailRule, RuleCondition, per-action-type action_config shapes, and evaluation outcome", "implemented": true, "prdRefs": [ "Functional Requirements #2" ] }, { "id": "F005", "description": "Validation schemas for rule create/update payloads: condition field/operator enums, action_config shape per action_type, on_no_match enum, regex pattern length cap", "implemented": true, "prdRefs": [ "Functional Requirements #9" ] }, { "id": "F006", "description": "Evaluator: case-insensitive equals/contains/starts_with/ends_with operators over condition fields", "implemented": true, "prdRefs": [ "Functional Requirements #2" ] }, { "id": "F007", "description": "Evaluator: matches_regex operator with try/catch compile (invalid = condition false, logged once per rule per process) and pattern length cap", "implemented": true, "prdRefs": [ "Functional Requirements #2" ] }, { "id": "F008", "description": "Evaluator: field accessors from parsed email \u2014 from_address, from_domain, to_address (any recipient), subject, body_text sliced to ~100KB", "implemented": true, "prdRefs": [ "Functional Requirements #2" ] }, { "id": "F009", "description": "Extractor: between/after/before templates with occurrence first/last, compiled to the same internal extractor as raw regex", "implemented": true, "prdRefs": [ "Functional Requirements #5" ] }, { "id": "F010", "description": "Extractor: raw regex extraction using capture group 1", "implemented": true, "prdRefs": [ "Functional Requirements #5" ] }, { "id": "F011", "description": "Extracted-value normalization: trim, collapse internal whitespace, lowercase; empty result = non-match", "implemented": true, "prdRefs": [ "Functional Requirements #5" ] }, { "id": "F012", "description": "Client matcher: normalized exact match against clients.client_name, excluding inactive clients", "implemented": true, "prdRefs": [ "Functional Requirements #5" ] }, { "id": "F013", "description": "Client matcher: fallback to client_name_aliases lookup (tenant-scoped, case-insensitive)", "implemented": true, "prdRefs": [ "Functional Requirements #5" ] }, { "id": "F014", "description": "Rule loader: single indexed query for active tenant rules ordered by position, filtered in-process by provider_ids (NULL = all)", "implemented": true, "prdRefs": [ "Functional Requirements #3", "Non-functional Requirements" ] }, { "id": "F015", "description": "Evaluation loop: first conditions-match executes action; resolved action stops evaluation; on_no_match=proceed continues to subsequent rules", "implemented": true, "prdRefs": [ "Functional Requirements #3" ] }, { "id": "F016", "description": "on_no_match=skip outcome: email skipped when the matched rule's extraction/classification fails", "implemented": true, "prdRefs": [ "Functional Requirements #3" ] }, { "id": "F017", "description": "on_no_match=fallback_destination outcome: ticket created using fallback_inbound_ticket_defaults_id", "implemented": true, "prdRefs": [ "Functional Requirements #3" ] }, { "id": "F018", "description": "Engine error isolation: any unexpected evaluator error logs a warning with rule id and falls through to the unmodified pipeline", "implemented": true, "prdRefs": [ "Non-functional Requirements" ] }, { "id": "F019", "description": "Pipeline hook in processInboundEmailInApp: rules evaluated on new-ticket path only, after thread matching fails, before resolveInboundTicketDefaults and sender matching", "implemented": true, "prdRefs": [ "Functional Requirements #3" ] }, { "id": "F020", "description": "Skip action: no ticket/comment/attachment processing; email_processed_messages row with processing_status='skipped' and metadata { ruleId, ruleName }", "implemented": true, "prdRefs": [ "Functional Requirements #4" ] }, { "id": "F021", "description": "Skip action works for tenants with no inbound_ticket_defaults configured (evaluated before defaults requirement)", "implemented": true, "prdRefs": [ "Functional Requirements #4" ] }, { "id": "F022", "description": "Rule-assigned client takes precedence over exact sender-contact match and domain match", "implemented": true, "prdRefs": [ "Functional Requirements #5" ] }, { "id": "F023", "description": "Contact attribution for rule-assigned client: sender contact within that client if present, else the client's primary contact", "implemented": true, "prdRefs": [ "Functional Requirements #5" ] }, { "id": "F024", "description": "Rule-assigned client flows through resolveEffectiveInboundTicketDefaults so client-level defaults overrides apply", "implemented": true, "prdRefs": [ "Functional Requirements #5" ] }, { "id": "F025", "description": "set_destination action: referenced inbound_ticket_defaults applied at the top of the destination cascade; sender matching still attributes client/contact", "implemented": true, "prdRefs": [ "Functional Requirements #6" ] }, { "id": "F026", "description": "Created tickets record appliedRuleId and clientMatchSource in tickets.email_metadata", "implemented": true, "prdRefs": [ "Functional Requirements #8" ] }, { "id": "F027", "description": "INBOUND_EMAIL_RECEIVED activity-log entry includes the applied rule name", "implemented": true, "prdRefs": [ "Functional Requirements #8" ] }, { "id": "F028", "description": "One structured log line per evaluated email: rules considered, rule matched, outcome", "implemented": true, "prdRefs": [ "Functional Requirements #8" ] }, { "id": "F029", "description": "Dangling reference handling: deleted fallback/destination defaults set or deleted/inactive aliased client degrades to non-match/proceed with warning", "implemented": true, "prdRefs": [ "Non-functional Requirements" ] }, { "id": "F030", "description": "AI classify OSS stub: dynamic loader returns no_decision (inboundReplyAcknowledgementDecider pattern)", "implemented": true, "prdRefs": [ "Functional Requirements #7" ] }, { "id": "F031", "description": "AI classify EE module: builds prompt from instruction + subject/from/body excerpt; no tenant client list in prompt", "implemented": true, "prdRefs": [ "Functional Requirements #7", "Security / Permissions" ] }, { "id": "F032", "description": "AI classify EE module: constrained JSON response parsing { decision: skip|assign_client|no_decision, extracted_client_name }", "implemented": true, "prdRefs": [ "Functional Requirements #7" ] }, { "id": "F033", "description": "AI assign_client decision resolves extracted_client_name through the same exact+alias client matcher", "implemented": true, "prdRefs": [ "Functional Requirements #7" ] }, { "id": "F034", "description": "allowed_outcomes enforcement: AI decisions outside the rule's allowed_outcomes are treated as no_decision", "implemented": true, "prdRefs": [ "Functional Requirements #7" ] }, { "id": "F035", "description": "AI failure handling: timeout/error/missing add-on/malformed response = non-match routed to on_no_match; ticket creation never blocks on AI", "implemented": true, "prdRefs": [ "Functional Requirements #7" ] }, { "id": "F036", "description": "AI token usage logged through the AI module's existing usage tracking", "implemented": true, "prdRefs": [ "Functional Requirements #7" ] }, { "id": "F037", "description": "Server action: list inbound email rules for tenant", "implemented": true, "prdRefs": [ "Functional Requirements #9" ] }, { "id": "F038", "description": "Server action: create rule with full payload validation", "implemented": true, "prdRefs": [ "Functional Requirements #9" ] }, { "id": "F039", "description": "Server action: update rule with full payload validation", "implemented": true, "prdRefs": [ "Functional Requirements #9" ] }, { "id": "F040", "description": "Server action: delete rule", "implemented": true, "prdRefs": [ "Functional Requirements #9" ] }, { "id": "F041", "description": "Server action: reorder rules (persist positions atomically)", "implemented": true, "prdRefs": [ "Functional Requirements #9" ] }, { "id": "F042", "description": "Server action: test rule \u2014 runs the shared evaluator against a draft rule + sample From/Subject/Body without persisting; returns per-condition pass/fail, extracted value, resolved client, final outcome", "implemented": true, "prdRefs": [ "Functional Requirements #9", "UX / UI Notes" ] }, { "id": "F043", "description": "Server actions: client name alias CRUD (list per client, create, delete) with uniqueness error surfaced", "implemented": true, "prdRefs": [ "Functional Requirements #9" ] }, { "id": "F044", "description": "RBAC: all rule/alias/test actions require the email-provider-admin permission", "implemented": true, "prdRefs": [ "Security / Permissions" ] }, { "id": "F045", "description": "UI: Inbound Rules section in email settings alongside provider list and InboundTicketDefaultsManager", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F046", "description": "UI: rules table with name, human-readable summary, mailbox-filter chips, active toggle, edit/delete", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F047", "description": "UI: reorder rules via up/down controls persisting position through the reorder action (drag-and-drop deferred: no dnd dependency in the repo)", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F048", "description": "UI: human-readable rule summary generator (e.g. \"From contains @huntress.com \u2192 assign client from subject\")", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F049", "description": "UI: inline active/inactive toggle on rule rows", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F050", "description": "UI: rule editor drawer with name, active toggle, mailbox multi-select (empty = all mailboxes)", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F051", "description": "UI: condition builder \u2014 add/remove rows of field + operator + value; matches-regex available as a normal operator", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F052", "description": "UI: action picker for the four action types", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F053", "description": "UI: AI action visible but disabled with upsell hint when EE + AI add-on is not active", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F054", "description": "UI: extraction config \u2014 source select, template select (between/after/before/regex), delimiter/pattern inputs, occurrence select", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F055", "description": "UI: destination picker backed by active inbound_ticket_defaults sets (for set_destination and fallback)", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F056", "description": "UI: AI action config \u2014 instruction textarea and allowed-outcomes checkboxes", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F057", "description": "UI: non-match behavior select; fallback destination picker shown only when fallback_destination selected", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F058", "description": "UI: validation errors from server schemas surfaced inline in the editor", "implemented": true, "prdRefs": [ "Functional Requirements #9" ] }, { "id": "F059", "description": "UI: live tester panel \u2014 sample From/Subject/Body inputs invoking the test-rule action", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F060", "description": "UI: tester results \u2014 per-condition pass/fail, extracted value, resolved client or no-match, final outcome", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F061", "description": "UI: tester alias quick-add \u2014 when extraction succeeds but no client resolves, offer adding the value as an alias with a client picker", "implemented": true, "prdRefs": [ "UX / UI Notes" ] }, { "id": "F062", "description": "UI: \"Matching aliases\" section on the client record next to inbound email domains, with add/remove", "implemented": true, "prdRefs": [ "UX / UI Notes" ] } ]