# Prospect Platform API — Phase 9 boundary Dependency-light JSON API for tenant-scoped prospect workflows and the Phase 8 bounded website-scanning, Phase 7 domain-intelligence, Phase 6 normalization/deduplication, and Phase 5 source-ingestion contracts. Core domain rules use Python's standard library and persistence is SQLite. Scan requests/results, where enabled, must remain auditable and fail closed; scanning never submits forms, executes JavaScript, or authorizes outreach. ## Run From this directory: ```bash python3 app/main.py # listens on http://127.0.0.1:8000 python3 -m unittest discover ``` Set `PROSPECT_API_PORT` or pass `--port`; set `PROSPECT_API_DB` or pass `--db` to override the default `prospects.db`. ## Endpoint contract All protected endpoints require the server-side session cookie. Every query is constrained by the authenticated user's `organization_id`; IDs from another tenant behave as not found and must not disclose whether a record exists. Phase 7 domain routes (all tenant-scoped) are `POST /api/v1/businesses/{id}/domains/check`, `GET /api/v1/businesses/{id}/domains/check?domain=...`, `GET /api/v1/domain-checks`, `GET /api/v1/businesses/{id}/domain-candidates`, and `POST /api/v1/businesses/{id}/domain-candidates/check-availability`. The current implementation is intentionally conservative: a successful address lookup is reported as `ok`, unresolved/empty results as `unknown`, and an availability check returns `unknown`/`not_configured` because no provider is enabled. Treat these as observation states, not ownership or availability claims. ## Phase 4 jobs and live logging (target contract) The intended job resource has a stable `job_id`, tenant/creator metadata, operation/payload fingerprint, `status`, `attempt`, timestamps, cancellation state, and terminal error/result metadata. Its lifecycle is `queued` → `running` → exactly one terminal state: `succeeded`, `failed`, or `cancelled`. State transitions and worker messages must be persisted transactionally with tenant and job identifiers; terminal jobs are immutable except for controlled retention/redaction. ### Idempotency and events Mutating job creation should require an idempotency key (for example, an `Idempotency-Key` header). The key must be scoped to the authenticated tenant and operation, stored with a request fingerprint, and return the original job/result for an exact replay. Reuse with a different payload must be rejected rather than creating a second job. Idempotency must cover side effects, not merely the HTTP response. Live events should be append-only and ordered per job with a durable integer `sequence`/cursor. A consumer can resume from the last acknowledged sequence, tolerate duplicate delivery, and detect gaps. Events must contain only safe operational detail; do not persist passwords, session cookies, API keys, or unnecessary prospect/contact data. ### Control and delivery semantics The MVP job routes are `POST /api/v1/jobs`, `GET /api/v1/jobs`, `GET /api/v1/jobs/{id}`, `GET /api/v1/jobs/{id}/events`, `POST /api/v1/jobs/{id}/cancel`, and `POST /api/v1/jobs/{id}/retry`. They are tenant-scoped and backed by SQLite persistence. Cancellation is best effort and must be race-safe with workers; retry creates a new attempt/lineage and must not repeat completed side effects. Polling supports a bounded cursor and backoff in the web monitor. An SSE endpoint may stream the same persisted sequence events with `Last-Event-ID`, heartbeats, disconnect/reconnect support, and polling fallback, but SSE is not implemented in this MVP. The current runtime has no durable job queue and no Redis/Celery integration. Its in-process/SQLite worker is single-instance, non-durable across process loss, and unsuitable for horizontal scaling or guaranteed execution; it is pilot-only. ### Phase 5 source adapter contract Phase 5 treats a source as a registered, reviewable capability rather than an arbitrary URL or scraper. Every adapter contract must identify: - a stable adapter/source ID and version, owner, permitted purpose, and terms/robots contact; - accepted discovery-query fields, result schema, provenance fields, and validation/error behavior; - request and concurrency rate limits, retry/backoff rules, timeout/size bounds, and a retention class for raw source records; - health signals and circuit-breaker states (`closed`, `open`, `half-open`), including fail-closed behavior when unhealthy; and - an explicit `dry_run` mode that validates and plans work without contacting a source or writing prospect facts. The initial safe adapters are `csv` and `manual_reference`. CSV input may be parsed and previewed; a manual reference records operator-supplied source identity, citation/reference, observed value, and timestamp. Neither adapter independently verifies a source or authorizes outreach. Raw source records should be retained immutably enough to reproduce the normalized result, with tenant/source/query identifiers, capture time, adapter version, and redaction/retention metadata; never retain secrets or unnecessary personal data. A source registry entry must include its terms owner, approval status/expiry, allowed tenants or scopes, rate-limit policy, retention class, and health/circuit policy. A discovery query is bounded and tenant-scoped, and its execution mode must be explicit (`dry_run` by default). A live network source is not implemented and must be rejected unless product, legal, and security approval is recorded and operations explicitly enables the registered adapter. No query, job acceptance, or successful parse may be described as network discovery. ### Phase 5 source controls (contract, not current live routes) Implementations should expose source/query/job state without leaking raw payloads across tenants, including source approval, terms, rate-limit, retention, health, and circuit-open reason. On rate-limit, terms, approval, or circuit failure, return a safe non-live outcome and preserve an audit event; do not silently retry against another source. Dry-run must be side-effect-free with respect to external sources and prospect facts. These are Phase 5 design requirements; the current MVP has no network adapter or discovery endpoint. ### Health and workspace - `GET /api/v1/health/live` — unauthenticated liveness check. - `GET /api/v1/auth/me` — current authenticated user and tenant. - `GET /api/v1/dashboard/summary` — tenant-scoped counts and score summary. ### Prospect and child intelligence records - `GET /api/v1/businesses` — paginated tenant list. Supports bounded `page`/`page_size`, text `q`, `score_min`/`score_max`, `website_class`, and `pipeline_stage` filters, with stable ordering. Responses contain `items`, `page`, `page_size`, and `has_next`; callers must not assume all records are returned. - `POST /api/v1/businesses` — manual business creation. `name` is required; website, email, phone, description, and other explicitly supported intake fields are optional. Normalization, scoring, deduplication, and suppression are enforced server-side. - `GET /api/v1/businesses/{id}` — tenant-scoped business detail, including the permitted child intelligence/evidence projection and current pipeline/review context. The business detail includes the supported child collections: `contacts`, `domains`, `websites`, `evidence`, `pipeline`, and `notes`. The child collection routes are: - `POST /api/v1/businesses/{id}/contacts` — manually add a contact; suppression matching marks a matching contact as do-not-contact. - `GET /api/v1/businesses/{id}/contacts/extract` — read the tenant-scoped Phase 9 official-site extraction projection. - `POST /api/v1/businesses/{id}/contacts/extract` — extract bounded public contacts from the approved official site/same-site pages and persist provenance-bearing observations; the request is passive and must not probe SMTP or send outreach. Suppression matching marks matches `suppressed`/`do_not_contact` and remains authoritative. - `POST /api/v1/businesses/{id}/domains` — manually add a domain observation. - `POST /api/v1/businesses/{id}/websites` — manually add a website observation/classification. - `POST /api/v1/businesses/{id}/evidence` — manually add evidence with its kind, claim, and source URL/reference. This records provenance supplied by the operator; it does not scan or independently verify the URL. - `POST /api/v1/businesses/{id}/notes` — add a manual note. Child records are subordinate to their parent business. A child ID is never sufficient authorization: the API verifies both the child ID and the parent business's organization. Do not use a missing source or a score as proof that a website or DNS check occurred. ### Pipeline, verification, and audit behavior - `POST /api/v1/businesses/{id}/pipeline` — record an allowed human workflow-stage transition, with server-side validation and an audit event. - `POST /api/v1/businesses/{id}/verify` — record the permitted human verification action and its audit event; it does not perform an external check. - Each business detail response returns the tenant-scoped child collections and current verification/pipeline context. Audit events are retained in the workspace audit log; the detail projection includes the relevant mutation context where supported. Pipeline state and verification are review metadata, not outreach authorization. Suppression always wins, and the API exposes no send/contact endpoint. State changes, child records, and notes are human-entered; they do not trigger discovery, scanning, or outbound messaging. ### Existing safety and intake routes - `POST /api/v1/suppressions` — add `{kind: email|domain|phone, value: ...}` for the current tenant. Future matching business creation is blocked. - `POST /api/v1/imports/preview` — preview `{rows: [...]}` without writing; reports accepted, duplicates, suppressed, and normalized rows. It is not an import/persistence endpoint. All SQL uses parameters and all responses are JSON. Scores include `score_version` and `score_factors` for traceability. Provenance is supplied by the operator/source record; the MVP does not validate external sources or independently refresh evidence. ### Phase 6 normalization and duplicate-review contract The server is the normalization authority. SA phone input is canonicalized using explicit `+27` context for local `0` numbers, with punctuation/spacing removed while retaining a display/original value. Location input retains the raw observation and derives a comparison form plus country/province/municipality/city tokens; missing or ambiguous locality must remain missing/ambiguous, not guessed. The normalization/schema version must be stored with derived values so reprocessing is deterministic. Exact duplicate keys are deterministic. `GET /api/v1/businesses/{id}/matches` compares only active businesses in the authenticated organization and produces a sorted, deterministic score, score version, and explainable reasons. Its default candidate cutoff is `0.72`; policy bands are `>=0.90` strong suggestion, `0.75–0.8999` review suggestion, and `<0.75` no suggestion. Fuzzy comparison is **suggestion-only** and there is no automatic merge at any score. `POST /api/v1/businesses/{id}/merge` requires an authenticated mutating-role user and an explicit target; the web client also requires a human confirmation. A production merge permission and server-verifiable confirmation token remain hardening work. Before a confirmed merge, the current route persists a tenant-scoped `merge_history` snapshot of the source business and child rows (`business_identifiers`, `contacts`, `domains`, `websites`, `evidence`, `pipeline_entries`, `interactions`, and `notes`), plus child IDs/counts, actor, and timestamp. It re-parents those children to the target without deleting source records; `GET /api/v1/merge-history` reads the ledger and `POST /api/v1/merge-history/{id}/reverse` restores the source/child links. Candidate queries, merges, snapshots, reversal, and history reads apply the organization predicate; a cross-tenant ID behaves as not found. Audit events record merge and reversal actions. Remaining limitations: the snapshot currently focuses on the source graph rather than a full two-parent conflict snapshot; the merge route does not yet enforce a dedicated merge permission or cryptographically bound confirmation payload; and preservation/conflict semantics need production-grade transactional and concurrency tests. It must not claim that normalization proves identity or that deduplication performs discovery or outreach. ## Pagination and filtering rules List and child-record endpoints are deliberately bounded. For business lists, use `page` (starting at 1) and `page_size` within the server-enforced maximum; invalid values are rejected rather than allowing an unbounded query. Supported filters are applied inside the tenant-scoped query before pagination: `q`, `score_min`, `score_max`, `website_class`, and `pipeline_stage`. The UI's page and filter controls are convenience clients, not authorization controls. A filtered page is not a count of the entire unfiltered tenant unless the response explicitly says so. ## Phase 7 domain-intelligence contract Domain observations are tenant-scoped, provenance-bearing inputs. Registrable-domain normalization must use a pinned/versioned PSL rather than a last-two-label heuristic. Preserve the original value and normalized form; return `unknown` when the PSL cannot classify a value (including an unknown/private suffix, public suffix, malformed name, single-label name, localhost, or IP literal). IDN/punycode and case/label handling must be deterministic and must not turn a subdomain into an independent business identity. DNS checks must expose an explicit status—`not_checked`, `pending`, `resolved`, `nxdomain`, `no_data`, `timeout`, `servfail`, `blocked`, or `error`—and never collapse failure or absence into a negative business fact. MX, NS, and TXT are independently uncertain observations: retain record type, normalized response, resolver/source, observed time, TTL if supplied, truncation/partial indicators, and error/uncertainty reason. A missing MX does not prove that email is unavailable; an NS result does not prove control; TXT content does not prove ownership. Any DNS cache must be bounded, tenant-safe, keyed by normalized name/type/class and resolver policy, and TTL-aware. Do not extend authority beyond the received TTL; expose `observed_at`, `expires_at`/freshness, and stale or refresh state. A cache hit is not a fresh lookup, and resolver policy/PSL version changes require invalidation or re-evaluation. There is no DNS resolver or cache service in the current runtime. Association confidence is a separate, explainable, versioned review signal—not DNS status, duplicate score, or identity proof. Candidate-domain generation must apply the organization predicate before comparison, reject public-suffix-only/malformed/IP candidates, avoid automatic attachment, and flag shared/parked/wildcard/homograph/sibling-subdomain and conflicting-evidence cases. Human accept/reject decisions, reasons, provenance, and confidence version must be auditable; no candidate may authorize outreach or verification. Domain availability is `unknown` unless the API reports a result from an authorized provider. The provider must be registered with current product/legal/security approval, terms owner, tenant scope, rate/concurrency limits, retention, health/circuit policy, and operational enablement checked at execution time. `nxdomain`, `no_data`, timeout, stale cache, or provider failure is not an availability result. Never purchase, reserve, contact, or report a domain as available from DNS alone. These safeguards are contract requirements only; no live availability provider is implemented. ## Phase 8 website-scanning contract Website scans are tenant-scoped, authenticated, bounded observations. Accept only `http` and `https`; reject credentials, unsupported schemes, malformed/localhost/single-label hosts, and disallowed IP literals. Resolve and validate the destination immediately before connecting, block loopback/private/link-local/multicast/reserved/cloud-metadata ranges, and repeat the protocol/host/DNS/IP checks for every redirect. Redirects must have a small fixed maximum and cannot escape the allowed protocol policy. DNS rebinding protections must validate the address actually used for the connection. Apply hard per-scan budgets for wall-clock time, connect/read timeouts, response/body bytes (including decompression), redirects, crawl depth, discovered links, and concurrency/retries. Crawl only explicitly allowed same-policy links; do not submit forms, send cookies or credentials, execute JavaScript, run plugins, or emulate a browser. Unsupported content, a budget exhaustion, timeout, DNS failure, redirect rejection, or partial response is an explicit `unknown`/`blocked`/`partial`/`error` result, never a successful empty page. Classifications must be conservative, explainable, and derived only from bounded fetched content. They are observations, not proof of ownership, identity, consent, deliverability, security, or contact permission. Persist the normalized URL, redirect chain, response metadata, observed time, scanner/policy/version, applied budgets, cache status, and uncertainty/error reasons; redact response bodies and secrets unless an approved minimal excerpt is required. Scan history and cache reads/writes require the same tenant predicate as business routes. Keys include normalized URL, scanner/policy version, and relevant request/redirect policy; entries are size- and retention-bounded, expose `observed_at` and freshness/expiry, and never make a cache hit look like a fresh scan. Invalidate or re-evaluate entries after policy, DNS, or scanner-version changes. No scan result may trigger enrichment, acquisition, verification, or outreach. ## Phase 9 official-site contact extraction contract The optional Phase 9 extractor is a passive, authenticated, tenant-scoped observation of a business's approved/public official-site origin. It may inspect bounded HTML and same-site contact/about pages only; it must not become a search engine, unrestricted crawler, or arbitrary URL fetcher. The extractor must use the existing SSRF-safe URL, redirect, content-type, and resource-budget controls, and must never submit forms, execute target JavaScript, use credentials/cookies, probe SMTP, issue SMTP `VRFY`/`EXPN`, send validation mail, or perform outreach. For each candidate, return/store the normalized address only with provenance (official-site/page URL, page or DOM context, extraction method, observed time, extractor/policy version) and an explainable confidence/reason list. Preserve uncertainty rather than inventing facts. `syntax_valid`/`syntax_invalid` is a parser outcome only. Role classification (`role`/`person`/`unknown`) and free-mail classification (`free_mail`/`business_domain`/`unknown`) are independent review labels; they do not prove identity, consent, ownership, or deliverability. MX/DNS is a separate observation with resolver/source, observed time, TTL/freshness where available, and one of `not_checked`, `resolved`, `nxdomain`, `no_data`, `timeout`, `servfail`, `blocked`, or `error`; MX absence or failure remains unknown and must never be treated as invalid or undeliverable. Exclude false positives before persistence and response: values in scripts/styles/comments or asset URLs/file names, example/test/placeholder domains, tracking/telemetry addresses, malformed schemes, and unrelated third-party pages. Enforce hard limits for total extraction time, pages/URLs, redirects, response and retained bytes, candidates per page/request, and concurrency. Suppression matching is server-side and tenant-scoped, before storing, returning, exporting, or presenting a candidate; suppressed contacts are marked do-not-contact and cannot be revived by a later classification or review action. Retention must be explicit and bounded for extracted values, page provenance, DNS/MX observations, caches, and audit records; logs must not contain full contact payloads when a redacted identifier is sufficient. Extraction results are suggestions only and do not create a send/contact capability. The API exposes no SMTP-probe, validation-message, outreach, or campaign endpoint. If the feature is disabled, unapproved, over limit, blocked, or uncertain, fail closed with an explicit status/reason rather than an empty successful result. The current MVP remains pilot-only until extraction limits, suppression enforcement, retention/deletion jobs, provenance/audit coverage, and tenant-isolation tests are production hardened. ## Phase 10 scoring contract Scoring is a tenant-scoped, deterministic derivation with three separate outputs: `score` (fit/ranking), `priority_band` (versioned threshold policy), and `eligibility` (whether a later, separately authorized workflow may act). A high score or priority band never authorizes contact. The API must calculate these values server-side from a named rule set and immutable rule-set version; weights, thresholds, required evidence, freshness windows, suppression precedence, and uncertainty handling are configuration, not undocumented code defaults. Each result should expose the rule-set ID/version and algorithm/version, calculation timestamp, input/evidence snapshot or stable references, contributing factors, points/weights, exclusions, band decision, eligibility decision/reasons, and stale/uncertain state. Explanations are reviewable lineage, not proof of identity, consent, deliverability, or permission. Normalize and round deterministically, define tie-breaking, and reject client-supplied score/band/eligibility/version fields. Eligibility is evaluated independently of score. Tenant-scoped suppression/do-not-contact is an unconditional ineligible result. Required evidence that is stale, expired, missing, blocked, partial, or uncertain must produce an explicit reason and fail closed according to the active rule set; it must not be converted into a zero, a positive signal, or an empty successful result. A score may remain visible for triage while eligibility is `ineligible` or `unknown`, and suppression must remain visible after recalculation. Recalculation must be an explicit authenticated operation, preferably represented by the existing tenant-scoped job contract for larger sets. It must capture the requested rule-set/version, input snapshot, actor/job/idempotency lineage, started/completed time, counts and failures, and before/after score, band, eligibility, and explanation changes. Every rule change and recalculation is auditable; retries cannot duplicate or erase history, and a partial run must be marked incomplete. Audit and explanation reads use the same organization predicate as business reads, and cross-tenant IDs/jobs/rule sets behave as not found. The current MVP's scoring surface is limited compared with the Phase 10 contract: production still needs an authorized rule-set management API, approval/activation and rollback semantics, immutable evidence snapshots, scheduled/durable recalculation, concurrency protection, deterministic migration of old scores, and comprehensive tests for suppression, stale/uncertain evidence, audit completeness, and tenant isolation. ## Phase 11 dashboard and review workflow contract Phase 11 adds the API contract for saved filters and review operations without weakening the tenant boundary. A saved filter is a named, tenant-owned record containing a validated, bounded predicate (search, score/status/pipeline/eligibility filters, sort, and page-size preference). Save/load/update/delete/list routes must scope by `organization_id`, reject unknown or unbounded fields, and never treat a client-provided filter ID as authorization. Sharing, if added, must be explicit and remain within the tenant; filter definitions must not store secrets. A review queue is a derived, tenant-scoped projection of businesses matching the saved/current filter. Its response must identify the predicate/snapshot, ordering, page or cursor, bounded `items`, and whether counts are page counts or full matching-set counts. Suppressed/do-not-contact records must remain visible as safety state when policy requires review, but are never contact-eligible. Merged/non-active businesses are excluded from merge candidates and must not be acted on as active records. Queue counts are not authorization and must be recomputed under the caller's tenant and permission scope. Bulk operations must accept only a bounded selection of IDs or a server-created immutable filter snapshot, enforce a maximum batch size before execution, and require preview followed by explicit confirmation. At execution time the server must re-check tenant ownership, permissions, suppression, active/merge eligibility, and current versions. Require an idempotency key or equivalent safe retry behavior, prevent duplicate side effects, and return a per-record result (`succeeded`, `skipped`, or `failed` with a safe reason) plus bounded totals. A request accepted or previewed is not completion. Bulk review actions do not create an outreach capability and must not auto-merge records. Clickable dashboard counts must link to the exact tenant-scoped predicate that produced them. The API must distinguish `page_count` from `matching_count`/`has_more`; clients must not turn a page count into a global total or silently drop eligibility/suppression criteria on navigation. Loading, stale, error, and unavailable counts are distinct from zero. Saved-filter changes, queue decisions, bulk preview/confirmation/execution, suppression/eligibility decisions, and merge/reversal operations require audit records containing tenant, actor, action, timestamp, filter/selection snapshot or hash, bounded counts, per-item outcomes, policy/version context, and a correlation/idempotency identifier. Audit reads use the same organization predicate and redact secrets and unnecessary personal/contact data. The current Phase 11 slice exposes `GET /api/v1/saved-filters`, `POST /api/v1/saved-filters`, `GET /api/v1/review-queue`, and `POST /api/v1/businesses/bulk-review`. Saved filters are durable and bounded, the queue is capped at 100 rows per request, and bulk verify/reject/assign accepts at most 100 explicit IDs. The slice remains pilot-grade: update/delete saved-filter handlers are not routed, review-queue results do not yet expose a full matching-set count or immutable filter snapshot, dashboard clickable-filter metadata is not a complete predicate, bulk execution has no preview/idempotency/per-record outcome contract, and bulk audit is one aggregate event. Do not infer stronger guarantees from the existing list filters. ## Phase 12 CRM API contract Phase 12 introduces tenant-scoped CRM records for pipeline state, append-only interactions, normalized outcomes, bounded reports, and a suppression center. All routes must use the authenticated session's `organization_id`; a business, interaction, outcome, report, export, suppression, cursor, or filter ID from another tenant behaves as not found. The server—not the web client—enforces role permissions, state transitions, suppression, batch/report limits, and redaction. Pipeline transitions use `new` → `contacted` → `qualified` → `proposal` → `negotiation` → `won`/`lost`. Any paused/disqualified state must be explicitly configured before use and requires a reasoned, authorized reopen. The API accepts only policy-approved transitions, rejects direct jumps and changes to merged/inactive records, and appends actor, before/after state, reason, timestamp, and correlation/idempotency metadata. Same-state retries are idempotent. Reopening a terminal-for-now state creates a new event; it never edits history. Interactions do not implicitly advance the pipeline. `won` and `lost` require an explicit outcome/reason. Interaction records contain a bounded safe summary, channel, occurred/recorded timestamps, actor, business/contact reference, provenance, and idempotency lineage. Outcomes are normalized to `connected`, `no_answer`, `left_message`, `meeting_booked`, `meeting_held`, `qualified`, `disqualified`, `won`, `lost`, or `other`. `other` is an explicit catch-all, not proof of success or failure; missing data must not be silently assigned a negative outcome. Corrections append a superseding event and preserve the original. `do_not_contact` is safety-critical and cannot be overridden by score, stage, a later outcome, or a client payload. Reports must require bounded date ranges and page/row limits and expose their tenant, timezone, `as_of`, freshness, filter snapshot, and semantics. State reports use the latest effective state per active business; interaction/outcome reports use `occurred_at`; counts distinguish events from distinct businesses and page counts from matching-set counts. Suppressed, merged, inactive, and unknown records are labeled and never counted as contact-eligible. Report/export requests and results are audited, report caches are tenant-keyed, and report data must not become an authorization shortcut. Suppression endpoints accept only approved normalized identifier kinds and record source, reason, actor, scope, and timestamps. Matching occurs before writes, responses, exports, reports, caches, and queues. Suppressed contacts remain visible as `suppressed`/`do_not_contact` for safety review; deletion or unsuppression requires an authorized, reasoned, audited operation and does not retroactively rewrite interaction history. No endpoint sends messages, probes SMTP, performs validation mail, creates campaigns, or schedules delivery. Outreach remains disabled and requires a separate approved product/security/legal design. Every CRM mutation and report/export operation emits an audit record with tenant, actor, action, target, before/after or bounded result, policy/version, timestamps, correlation/idempotency ID, and safe reason. Audit and retention reads use the same organization predicate. CRM records, contact references, suppression decisions, report snapshots, and audit details require explicit retention classes, deletion/legal-hold semantics, and redacted logs. These are the Phase 12 contract; production readiness additionally requires durable migrations, worker/retry behavior, report reproducibility, export authorization, retention jobs, and transition/outcome/suppression/cross-tenant tests. ## Phase 14 draft-only outreach API contract Phase 14 is a preparation contract, not a delivery feature. A future authenticated route may create a tenant-scoped outreach **draft** from a bounded approved evidence set, but the current API exposes no send, delivery, campaign, SMTP, validation-message, or autonomous follow-up endpoint. The server must reject any request that attempts to send or that treats draft creation/approval as delivery. `AUTOMATED_OUTREACH_ENABLED=false` is the no-send default in Compose and must be enforced server-side, not only by the UI. Draft creation must evaluate and persist gate results for tenant/recipient scope, normalized suppression/do-not-contact, consent or other documented legal basis, jurisdiction/channel policy, evidence permission/freshness, current eligibility, provider approval, and content policy. Suppression is an unconditional deny. Public contact data, pipeline state, score, verification metadata, or AI evidence citations do not establish consent, lawful basis, deliverability, or permission to contact. A blocked, missing, stale, conflicting, or uncertain gate returns an explicit non-send reason rather than an empty success. Provider configuration is server-side and deny-by-default. A registered provider must include an allowlisted ID/version, purpose/capability, permitted tenant/data class, processing region and retention terms, timeout/payload limits, per-tenant and global rate caps, daily message/cost ceilings, health/circuit state, approval owner/expiry, and explicit operations enablement. Credentials are secret-manager references only and must never be accepted from clients or returned in responses/logs. Fallbacks, if ever enabled, must be pre-approved for the same purpose, data class, policy, caps, citations, and authority; provider failure, timeout, quota, circuit-open, or expired approval fails closed. Every draft must retain recipient/channel, bounded redacted content or a safe content hash, evidence IDs/source citations, exact evidence snapshot hash and observed times, policy/provider versions, gate outcomes, actor, approval status/expiry, and correlation ID. Drafts are immutable or versioned: an edit creates a new version and invalidates approval. Approval/rejection is an explicit authorized human operation bound to the unchanged draft/evidence/policy hash; it must re-check tenant scope, suppression, legal/consent state, freshness, and provider approval, and record actor, time, reason, before/after status, and audit event. An approved draft still requires a separately authorized future send operation. Any future send or other side effect must require an `Idempotency-Key` scoped to tenant, operation, draft version, recipient, provider, and policy fingerprint. Exact retries return the original result; a different request under the same key is rejected. Enforce rate/message/cost caps before attempts and across retries, fallbacks, and workers; use bounded retry/backoff and circuit breaking. Audit draft/gate/approval/provider/cap/suppression events with safe per-item outcomes and redacted payloads. No route may infer completion from request acceptance. The Phase 14 implementation remains documentation-only/pilot preparation: there is no draft persistence/API, consent ledger, legal-policy evaluator, configured provider, durable approval queue, delivery adapter, bounce/complaint feedback, secret manager, or production-grade audit/retention workflow in the current runtime. Production requires those components plus DPA/provider and jurisdictional legal review, suppression synchronization, kill switch, rollback/revocation, deletion/legal-hold verification, and integration tests proving no-send default, citation/hash binding, stale/uncertain gate failure, cap enforcement, idempotent replay/conflict rejection, approval expiry, and cross-tenant isolation. ## Remaining limitations and production migration work SQLite is a pilot store with an MVP job/event schema but no production migration runner, durable queue, scheduler, worker lease/recovery, durable backup command, or tested restore workflow. Authentication currently uses a development password fallback and does not by itself provide production Argon2id, MFA, CSRF protection, rate limiting, or a complete retention-grade audit system. Website scanning remains subject to pilot limits: no production egress proxy/isolation, distributed crawl coordinator, hardened resolver, or compliance-grade scan-history retention. Production work must add SSRF/DNS-rebinding/redirect-chain tests, egress policy, authenticated history/cache isolation, budget/abuse enforcement, durable result retention/deletion, observability, reviewed content/robots/terms policy, and the Phase 7 PSL/DNS/availability controls. Redis/Celery/Postgres remain future options, not implemented dependencies. ## Phase 13 optional AI assistance API contract AI assistance is an optional, authenticated, tenant-scoped drafting capability. It may summarize or classify already-held evidence and propose review text; it must not discover facts, verify identity/ownership/deliverability, calculate authoritative score/eligibility, mutate CRM state, merge records, acquire domains, send messages, create campaigns, schedule follow-ups, or perform any autonomous CRM/outreach action. The current Compose runtime does not configure an AI provider or expose a production AI worker; any future route must be explicitly enabled and documented rather than inferred from a provider setting. Provider selection is server-side and deny-by-default. A primary provider and optional fallback may be configured only from an allowlist of approved provider IDs. Each provider registration must include capability/purpose, model/version, tenant/data-class scope, processing region and retention terms, timeout/token/request budgets, rate and cost limits, approval owner/expiry, health/circuit state, and operational enablement. Fallback may run only for the same approved purpose and input data class; it must preserve the same tenant scope, redaction policy, evidence set, citation contract, and authority level. Provider credentials are secrets and must never appear in request payloads, prompts, responses, logs, Compose, or committed environment files. Provider outage, timeout, quota, policy rejection, or expired approval returns an explicit `unavailable`/`unknown` result and does not silently invent facts or present invented facts as supported claims. Before a request leaves the tenant boundary, minimize and redact data: include only the bounded evidence fields required for the task; remove credentials, tokens, session data, secrets, unnecessary contact values, raw page bodies, and unrelated personal data; and enforce input/output size limits. Store a redacted request/policy fingerprint, not a sensitive prompt. Every suggestion must carry tenant-scoped evidence IDs/citations, a hash of the exact evidence snapshot used, observed/captured times, uncertainty/conflict reasons, provider/model/version, and policy version. Hashes and citations provide reproducible lineage, not truth or independent verification. If evidence is absent, suppressed, stale, conflicting, blocked, or uncertain, preserve that state and return no unsupported claim. AI output is untrusted until an authorized human approves it. Approval/rejection must be an explicit tenant-scoped operation with actor, time, reason, output/version, evidence hash/citation set, and before/after value in the audit trail. At approval time re-check authorization, suppression, evidence freshness, provider/policy approval, and hash equality; changed evidence requires a new review. A rejected or expired suggestion must not be applied by retry, fallback, cache, or background work. Approval never converts a citation into proof, consent, deliverability, or outreach permission. AI records, prompts/fingerprints, outputs, citations, evidence snapshots, approvals, and audit events require explicit retention classes, deletion/legal-hold behavior, tenant-keyed access, and redacted operational logs. Preserve enough hashed lineage to explain an approved result without retaining unnecessary source content. Cross-tenant business, evidence, suggestion, approval, provider, job, cache, and audit IDs behave as not found. Production requires provider contracts/DPA review, secret isolation, immutable/tamper-evident audit, deletion verification, cost/rate monitoring, prompt-injection and hallucination tests, human-review SLAs, kill-switch procedures, and durable worker/retry semantics; none are implied by the current MVP.