A safety-first Phase 8 design/implementation boundary for **manual**, evidence-led prospect qualification, controlled source ingestion, bounded website scanning, and domain intelligence review. Phase 8 website scanning is a conservative observation workflow: it never submits forms, executes JavaScript, follows unsafe protocols, or authorizes outreach. **Automated outreach is disabled, and no live source may be enabled without explicit approval.**
- Server-side normalization, conservative website classification, exact deduplication, versioned scoring, and suppression checks. Phase 6 documents the SA phone/location canonical forms and the review-only fuzzy-match contract.
- Phase 5 source-ingestion contract: an approved source registry owns adapter terms, rate limits, retention, and health/circuit policy; CSV and manual reference adapters are the safe initial adapters.
- Discovery queries are recorded as bounded, auditable intent and dry-run plans. Recording a query does not perform network discovery or imply that results exist.
1. A permitted workspace member manually creates or reviews a prospect.
2. The business detail response is the aggregate record for that tenant; related intelligence/evidence rows are returned only through the tenant-scoped detail surface.
3. Each manually entered intelligence item should retain its source/provenance (for example, source label or URL, observed value, and captured/verified time). Missing provenance is a data-quality limitation, not permission to infer facts.
4. Members use the pipeline state and notes to coordinate human review. A state change or note is an application event and is included in the record's audit/activity history where exposed by the API.
5. Suppression remains a hard safety boundary. Suppressed or unreviewed records must not be treated as eligible for contact.
The API applies the organization/tenant boundary server-side to list, detail, child-record, notes, pipeline, and audit reads and writes. Clients must use the returned pagination metadata and follow `next`/`previous` links or tokens rather than assuming that one response contains the whole tenant dataset. See `apps/api/README.md` for the route contract and limits.
The planned asynchronous contract is: create one tenant-scoped job, return a stable job identifier, and move it through `queued` → `running` → a terminal state (`succeeded`, `failed`, `cancelled`). Each accepted request should carry an idempotency key whose scope and request fingerprint prevent duplicate jobs while allowing a safe replay of the original result. A job should persist append-only events with a monotonically increasing per-job sequence number, timestamp, level/type, safe message, and job/tenant identifiers.
Clients should poll a tenant-scoped job status/events endpoint using `after_sequence` (or an equivalent cursor), with bounded backoff and terminal-state handling. SSE is a planned low-latency delivery option, not a current implementation; polling remains the compatibility fallback. Cancellation and retry must be explicit, authorized controls: cancellation is cooperative and may finish as `cancelled` or report that the job is already terminal; retry creates a new attempt while retaining the original job/idempotency lineage and must not duplicate side effects.
The current MVP has SQLite job/event persistence, job status/list/detail and event APIs, cancellation/retry controls, and a browser job monitor that polls while work is active. SSE is not implemented; it remains a future delivery optimization over the persisted cursor. There is no Redis/Celery worker: the current in-process worker is suitable only for development/pilot use and must not be treated as durable, horizontally scalable execution.
Open `http://127.0.0.1:8080`. Set `window.API_BASE` in the browser console to `http://127.0.0.1:8000` when testing the authenticated API locally, then sign in with the configured workspace credentials.
The protected calls require the authenticated session cookie. Exact child-record, notes, pipeline, and audit routes are documented in `apps/api/README.md` and are never cross-tenant addressable by changing an ID.
Compose passes the optional `BOOTSTRAP_ADMIN_EMAIL` and `BOOTSTRAP_ADMIN_PASSWORD` values to the API. Set both in an untracked `.env` only when provisioning a fresh instance, then remove them and rotate the password after the bootstrap admin is created. No credentials belong in this repository.
Authenticated browser requests use a server-side session cookie; login creates a session and logout invalidates it. The liveness endpoints (`GET /api/v1/health/live` and `GET /healthz`) intentionally remain unauthenticated so Docker, ingress, and monitoring health checks can use them. Authentication is not a substitute for tenant/authorization checks: protected routes must enforce the session and organization boundary server-side.
Phase 5 defines a source adapter contract and registry; Phase 8 adds a bounded website-observation adapter, but it does not implement general network discovery, enrichment scheduling, or a live external-source adapter. A source adapter must declare its identity, terms owner, permitted purpose, rate limits, retention class, query/result schema, dry-run behavior, and health/circuit controls. CSV and manual reference adapters may be used for operator-supplied data; they must preserve source attribution and raw source records, and must not silently turn preview data into outreach or verified facts.
A discovery query is a tenant-scoped, bounded, auditable request that can be validated and dry-run without contacting a source. Any live source requires explicit product/legal/security approval, a registered adapter, and an operational enablement decision; absent all three, execution must fail closed. Circuit-open, rate-limit, terms, or approval failures must produce a safe non-live result. Raw source records are retained only under the approved retention class and must exclude secrets and unnecessary personal data.
SQLite, the in-process worker, and the named local volume are suitable for the pilot only; production migration, durable queue/worker leases, event retention/backup, SSE delivery, and tested backup/restore remain unfinished. Redis and Celery are not implemented. The development password fallback is PBKDF2 rather than production Argon2id. Before production, complete the gates in `docs/SECURITY.md` and `docs/OPERATIONS.md`, including MFA, TLS, CSRF protection, source approval and terms review, rate limiting, circuit monitoring, tenant-scoped job/event authorization, idempotent side-effect handling, durable raw-source/audit retention, SSRF-safe fetching if a future scanner is approved, and tested backups/restores.
## Phase 6 normalization and deduplication boundary
Normalization is deterministic and versioned. For South African data, phone values are stripped to digits, local 10-digit `0` forms and `00 27` forms are converted to canonical `+27...`, and unknown international numbers retain their explicit country code; presentation punctuation must not create a second identity. Locations derive whitespace/case/diacritic-folded province, city, and suburb fields. A normalized value is not proof that the underlying observation is correct.
Exact keys (for example, canonical domain, email, or phone) may identify duplicate candidates. Fuzzy matching is deterministic and suggestion-only: the same inputs and normalization version produce the same candidate, score, and reason. A suggested match must never merge automatically. Use the documented thresholds: `>=0.90` is a strong suggestion, `0.75–0.8999` is a review suggestion, and `<0.75` is not surfaced as a suggestion. A human with permission must explicitly confirm each merge.
Every confirmed merge must create a tenant-scoped, immutable-enough merge snapshot before mutation, recording the surviving and absorbed IDs, normalized comparison inputs, score/reasons, acting user, timestamp, and schema/normalization versions. The operation must be reversible from that snapshot. It must preserve or re-parent every child, evidence item, provenance/source-record link, note, pipeline/audit history, and original source identity; conflicts remain visible for human resolution rather than being silently overwritten. Cross-tenant candidates are never comparable or mergeable, and each suggestion, confirmation, rejection, reversal, and preservation/conflict decision belongs in the audit trail.
The MVP now exposes deterministic match suggestions at `GET /api/v1/businesses/{id}/matches`, explicit merge confirmation in the web review dialog, tenant-scoped merge history, and `POST /api/v1/merge-history/{id}/reverse`. The implementation remains a pilot boundary: hardening is still needed for a dedicated merge permission, stronger server-side confirmation semantics, full snapshot conflict handling, and production-grade rollback guarantees. Do not describe a normalized or suggested match as verified identity, discovery, enrichment, or outreach authorization.
Domain intelligence is an observation and review aid, not proof of business identity, control of a domain, or availability. A domain normalizer may derive a lowercase ASCII/Unicode comparison form and a **registrable domain** using a versioned Public Suffix List (PSL). The PSL is an input with update/version drift: unknown, private, malformed, single-label, localhost, and IP-literal values must remain unresolved rather than guessed. A subdomain is not automatically a separate candidate, and a public suffix itself is never a registrable domain.
DNS status is explicit: `not_checked`, `pending`, `resolved`, `nxdomain`, `no_data`, `timeout`, `servfail`, `blocked`, and `error` describe the check outcome, not a business conclusion. MX, NS, and TXT observations may be absent, partial, truncated, stale, resolver-dependent, or blocked; no record is not proof that mail, delegation, ownership, or a business relationship is absent. Store the resolver/source, observed time, TTL where supplied, and uncertainty/error metadata. Caches must be bounded and keyed by normalized query/type/class plus resolver policy, honor an observed TTL without extending authority, and expose freshness/staleness; cached data must never be presented as a fresh check.
Association confidence is separate from DNS status and from duplicate score. It must be derived from explainable, tenant-scoped evidence (for example, an operator citation, an exact business-domain observation, or corroborating DNS facts), retain the algorithm/version and uncertainty reasons, and remain suggestion-only. Candidate generation must reject cross-tenant records, public-suffix-only values, malformed or IP-only inputs, and suppressed/merged targets as applicable; it must not auto-attach a domain or infer ownership from a shared, parked, wildcard, sibling-subdomain, homograph, or merely resolvable domain. Every candidate needs human review, provenance, and an auditable accept/reject decision.
The platform must not claim that a domain is available, unregistered, or safe to acquire without an explicitly authorized availability provider registered with current product/legal/security approval, terms, tenant scope, rate limits, retention, and operational enablement. DNS `nxdomain` or `no_data` is not an availability result. Provider outages, rate limits, stale responses, conflicting results, and unknown status must remain `unknown`/`unavailable`, fail closed, and never trigger purchase, outreach, or automated follow-up.
Phase 7 remains a documentation/contract boundary in this MVP: there is no live DNS resolver, PSL-backed enrichment worker, cache service, or availability provider in Compose. Production work still includes selecting and versioning the PSL, implementing bounded DNS resolution and TTL-aware cache invalidation, defining MX/NS/TXT parsing and uncertainty retention, adding association review/permission/audit tests, and completing an approved availability-provider integration with SSRF/network egress controls, monitoring, retention, and incident/rollback procedures.
Website scanning is a bounded, tenant-scoped observation—not a crawler, browser, verifier, or outreach mechanism. A scan may fetch only `http` and `https` URLs after strict parsing and normalization. It must reject credentials, non-web schemes (`file:`, `ftp:`, `gopher:`, `data:`, `javascript:`, and similar), malformed hosts, localhost, IP literals where policy disallows them, and targets in loopback, private, link-local, multicast, reserved, or cloud-metadata ranges. DNS is resolved immediately before connection and the destination is revalidated at connection time; every redirect is limited, normalized, and revalidated for protocol, hostname, DNS, and IP range before it is followed. DNS answers must not be trusted from the initial validation alone (including rebinding changes).
Each scan enforces hard budgets: total wall-clock/request time, response bytes, body bytes retained, redirect count, and page/link crawl count and depth. Budgets apply across redirects and discovered links, with bounded concurrency, retries, and response decompression; a limit, timeout, DNS error, unsupported content type, or partial fetch produces an explicit incomplete/unknown outcome rather than an empty result. The scanner fetches HTML and other explicitly allowed small resources only; it does not submit forms, send credentials, execute JavaScript, load browser plugins, or perform arbitrary subresource requests.
Classifications are conservative and explainable. `unknown`, `blocked`, `timeout`, `partial`, and `error` remain distinct from a positive observation. A page can be classified only from bounded fetched content and must retain URL, redirect chain, response metadata, observed time, scanner/policy version, limits, and uncertainty reasons. A detected contact form, script, tracking tag, or business phrase is an observation—not proof of ownership, consent, deliverability, safety, or permission to contact.
Scan history is tenant-scoped and append-oriented. Results and cache entries are keyed by normalized URL plus scanner/policy/version inputs, bounded by size and retention, and expose `observed_at`, freshness/expiry, and whether a result came from cache. A cache hit is never represented as a fresh scan; policy, DNS, or scanner-version changes require revalidation/invalidation. History must not leak response bodies, secrets, cookies, authorization headers, or unnecessary personal data across tenants.
The website scanner remains a pilot boundary. Compose does not provide a production egress proxy, durable scan queue, distributed crawl coordinator, hardened DNS resolver, or compliance-grade result store. Production still requires independent SSRF testing (including DNS rebinding and redirect chains), egress/network policy, resource isolation, durable retention/deletion, authenticated scan-history authorization, rate limits and abuse controls, observability, and a reviewed policy for content types, robots/terms, caching, and incident response. Scans must never trigger acquisition, verification, enrichment, or outreach automatically.
## Phase 9 public official-site contact extraction boundary
Phase 9 adds a **passive, suggestion-only** contact-observation workflow. When explicitly enabled, extraction may inspect bounded HTML from the business's approved/public official-site origin and its same-site contact/about pages; it is not general web search, crawling, enrichment, identity verification, or outreach. Only public page content and explicitly permitted `mailto:`/visible contact values may be considered. Do not submit forms, authenticate, bypass access controls, probe SMTP, send test messages, or contact a person or organization.
Every candidate contact must retain provenance: source URL and page location/context, extraction method, observed time, scanner/extractor and policy versions, and the exact uncertainty/reason code. Confidence is an explainable review signal, not deliverability, consent, ownership, or permission to contact. Classify role addresses separately from person addresses and classify free-mail domains separately from business-domain addresses; neither classification is proof of identity. Syntax validation is only a parse result. MX/DNS status is independently uncertain (`not_checked`, `resolved`, `nxdomain`, `no_data`, `timeout`, `servfail`, `blocked`, or `error`), and no MX result may be presented as deliverability.
False-positive exclusions must reject or quarantine values from asset URLs, image/file names, scripts/styles, example/test/placeholder domains, documentation text, tracking addresses, and malformed or unsupported schemes. Apply tenant-scoped suppressions before a candidate is persisted, returned, exported, or queued for review; suppressed values remain do-not-contact and suppression always wins over confidence, role, syntax, MX, pipeline, or verification state. Extraction is bounded by per-request and aggregate page/URL, byte, time, redirect, candidate, and concurrency limits. Store only the minimum contact value and lineage required for review, apply a documented retention/deletion class, and redact secrets and unnecessary personal data from logs and audit events.
Phase 9 does not authorize automated outreach. There is no SMTP probing, SMTP banner/VRFY/EXPN check, email validation message, send endpoint, campaign queue, or follow-up action. An extracted address is an observation requiring human review and explicit policy authorization before any separate future contact workflow.
Phase 10 separates **fit score**, **priority band**, and **contact eligibility**. A score is a deterministic ranking signal; it never authorizes contact. Rules are represented by a named, versioned rule set with explicit weights, thresholds, band definitions, eligibility gates, freshness policy, and suppression behavior. The active rule-set identifier/version is stored with each result so a historical score can be explained without silently applying today's policy.
A reproducible calculation uses the tenant-scoped business snapshot, normalized values, eligible evidence observations, rule-set/version, algorithm/version, and calculation time/freshness inputs. Explanations must retain the contributing factors, normalized inputs or evidence references, weights/points, exclusions, uncertainty reasons, and the final band. Do not accept a client-submitted score, band, eligibility flag, or rule version as authoritative.
Priority bands are policy labels (for example, high/medium/low or an explicitly configured equivalent) and must be derived from the versioned thresholds. Eligibility is evaluated separately and fail-closed: suppression/do-not-contact, stale or expired required evidence, unresolved/uncertain required signals, missing policy prerequisites, and authorization/tenant failures can make a prospect ineligible regardless of score. `suppressed` always wins and must remain visible; stale and uncertain observations must not be silently treated as absent or positive.
Recalculation is an explicit, tenant-scoped operation. It must snapshot the input/rule versions, record before/after score, band, eligibility, explanation, actor/job, timestamp, and reason in the audit trail, and be idempotent or safely repeatable. A policy/rule change must not rewrite history without an auditable recalculation; partial or failed recalculation must report its incomplete state rather than presenting mixed results as current.
Phase 10 remains a pilot boundary unless the runtime exposes all of the above controls end to end. Production work includes administrative rule-set lifecycle/approval, immutable calculation inputs, deterministic rounding/tie-breaking, scheduled recalculation with leases, retention and export semantics for explanations/audit, and regression tests proving suppression, stale, uncertain, and cross-tenant isolation behavior. See the API, security, and operations contracts for the authoritative safeguards.
## Phase 11 dashboard and review workflow boundary
Phase 11 defines the operator-facing review workflow around **saved filters**, a tenant-scoped **review queue**, and deliberately bounded bulk actions. A saved filter is a named, server-persisted filter definition (including search, score/status/pipeline/eligibility criteria, sort, and page-size preferences) owned by one tenant. Save, load, rename, and delete operations must authorize against the authenticated tenant; filter definitions are configuration, not authorization, and must not contain secrets or unbounded query instructions.
The review queue is a derived view of the current tenant's matching businesses. Queue counts and rows must use the same filter predicate, exclude records the caller cannot read, and make suppression and merge state explicit. Suppressed/do-not-contact records are never eligible for contact or bulk contact action; merged/non-active records are not merge-eligible. Match suggestions remain review-only: a score cannot auto-merge, and every merge requires an explicit permitted human confirmation and a reversible, tenant-scoped snapshot.
Bulk actions must be bounded by a server-enforced maximum, an explicit set of tenant-scoped record IDs or an immutable filter snapshot, and a preview/confirmation step. The API must re-check authorization, suppression, merge eligibility, and current record state at execution time, use an idempotency key or equivalent safe retry control, and return per-record outcomes rather than claiming that the whole selection succeeded. A filtered count is not permission to act on an unbounded result set, and bulk actions must never imply outreach or bypass review.
Dashboard counts are clickable only when their scope and query semantics are clear. A click must carry the exact tenant-scoped predicate that produced the count; it must not silently broaden to all records. Labels must distinguish the current page from the full matching set, and loading, stale, unavailable, and error states must not be rendered as zero. Every saved-filter mutation, queue decision, bulk preview/confirmation/result, suppression decision, and merge/reversal must emit an audit event with tenant, actor, action, timestamp, filter/selection snapshot, bounded counts, per-item outcomes, and relevant policy/version identifiers. Audit details must be redacted and tenant-scoped.
The Phase 11 slice now provides tenant-scoped saved-filter storage/listing, a bounded review-queue read, clickable dashboard filter metadata, and a bounded bulk review route (`POST /api/v1/businesses/bulk-review`) for verify/reject/assign. The current implementation is still pilot-grade: saved-filter update/delete routes are not wired, the queue does not yet expose full matching-set counts or filter snapshots, dashboard click metadata is not a complete predicate contract, bulk actions lack preview/idempotency/per-record outcomes, and audit coverage is aggregate for bulk operations. Production work requires those hardening items plus regression tests for suppression precedence, merge eligibility, cross-tenant IDs, stale counts, and partial bulk failure.
## Phase 12 CRM pipeline, interactions, outcomes, reporting, and suppression center
Phase 12 adds the CRM coordination contract around a tenant-scoped pipeline, an append-oriented interaction timeline, normalized interaction outcomes, bounded reporting, and a suppression center. These features are review and record-keeping tools; they do **not** turn the platform into an outreach system. Every read, write, export, report, and background operation must carry the authenticated `organization_id` scope, and a child ID, report ID, filter, or aggregate count is never authorization.
### Pipeline state and transition rules
The canonical lifecycle is `new` → `contacted` → `qualified` → `proposal` → `negotiation` → `won` or `lost`. The API exposes these configured stages; any future paused/disqualified state must be explicitly added to the tenant's stage configuration and may be reopened only by an authorized human with a reason. A transition must name the target state, actor, timestamp, and reason/source; the API validates transitions server-side and records the before/after state in the audit trail. Repeating the current state is idempotent, not a new transition. Direct jumps, client-supplied history, edits to historical transitions, and transitions for merged/inactive records are rejected. Reopening `lost`, `paused`, or `disqualified` creates a new transition and does not rewrite history.
An interaction may suggest a state change, but it never changes pipeline state implicitly. A state change and its related interaction/outcome are separate auditable events, and a failed or partial write must not leave a fabricated outcome. `won`/`lost` require an explicit outcome and reason; `won` is not proof of payment or fulfillment. Suppression/do-not-contact overrides every pipeline state and makes contact eligibility false.
### Interactions and outcome taxonomy
Interactions are append-only, tenant-scoped records of an operator-observed event. The record should retain the business/contact reference when known, channel (`note`, `phone`, `email`, `meeting`, or `other`), occurred time, actor, bounded redacted summary, provenance, and correlation/idempotency key. The current product does not send or validate communication: an interaction records what an operator says happened, not what the platform performed.
Outcomes are normalized and mutually explicit: `connected`, `no_answer`, `left_message`, `meeting_booked`, `meeting_held`, `qualified`, `disqualified`, `won`, `lost`, and `other`. `other` is an explicit catch-all, not proof of success or failure; missing/unknown data must not be silently converted to a negative outcome. `disqualified` and `lost` require an explicit reason under the configured workflow. `do_not_contact` is a separate safety state, immediately creates or confirms tenant suppression, and cannot be downgraded by a later positive outcome. Free-text notes supplement but do not replace the taxonomy; corrections append a new record rather than mutating history.
### Reporting, audit, tenant scope, and retention
Reports use an explicit `as_of` time, tenant scope, timezone, date interval, and inclusion policy. Pipeline reports count the latest effective state per active business; interaction reports count events by `occurred_at` (not ingestion time); outcome reports count normalized outcomes and may include a separate `unknown` bucket. Suppressed, merged, deleted, and inactive records must be labeled and excluded from contact-eligible totals; they must not silently disappear from safety/audit counts. Page counts, matching-set counts, and distinct-business counts are different metrics and must be named. Late-arriving or corrected interactions preserve original and corrected timestamps and are never double-counted without an explicit correction policy.
Audit events cover pipeline transitions, interaction/outcome creation or correction, suppression changes, report/export requests, and report results. Store tenant, actor, target, action, timestamps, safe reason, bounded filter/as-of snapshot or hash, policy/version, correlation/idempotency ID, and per-item outcomes where applicable. Redact full contact values and free text unless required for the approved purpose. Retain CRM records, suppression decisions, interaction provenance, report snapshots, and audit events according to the approved tenant/data-retention schedule; deletion or legal-hold behavior must be explicit and auditable. A report cache is tenant-keyed, bounded, and labeled with its `as_of`/freshness; it is never a live authorization decision.
### Suppression center and remaining outreach limitations
Suppression is a tenant-scoped deny list for email, domain, phone, and other approved identifiers. Matching is normalized server-side and must run before persistence, display, export, report inclusion as eligible, queueing, or any future action. Suppression wins over pipeline state, outcome, score, verification, cached data, and operator intent. The center must show the source, reason, actor, created/updated time, scope, and effective status; removal or expiry requires explicit authorization, reason, audit, and re-evaluation. Existing records remain visible as **Do not contact** and are not silently deleted. No Phase 12 route may send email/SMS, probe SMTP, validate an address by message, create a campaign, schedule follow-up delivery, or imply consent/deliverability. Any future outreach requires separate product, legal, security, and operational approval and must remain disabled by default.
Phase 12 remains pilot-grade until transition validation, immutable interaction/outcome history, suppression precedence, report definitions/timezones, retention/deletion jobs, export controls, idempotent writes, and cross-tenant regression tests are exercised end to end. The current Compose stack still has no durable CRM worker, scheduler, delivery provider, or outreach capability.
Phase 13 adds optional, human-reviewed AI assistance for summarization, classification, and evidence-oriented suggestions. AI is a drafting aid, not a source of truth, verifier, identity resolver, score/eligibility authority, or CRM actor. It is disabled unless an explicitly configured provider and tenant-scoped policy permit the requested operation. A provider failure, timeout, quota/rate limit, approval expiry, missing evidence, or ambiguous result fails closed to `unknown`/`unavailable`; it must never be represented as a successful empty result.
Provider configuration is optional and deny-by-default. A deployment may configure a primary provider and an optional fallback, but each provider must have an approved purpose/capability, tenant scope, data-processing/retention terms, region/egress policy, model/version, timeout/token budget, rate limit, cost ceiling, and operational enablement. Fallback is permitted only to another pre-approved provider for the same purpose and data class; it must not broaden tenant scope, retention, prompt data, or authority. No provider credentials belong in source, Compose files, logs, or committed `.env` files. The current Compose stack does not provision an AI provider; production enablement remains gated configuration work.
AI requests must minimize data before transmission: send only the fields and evidence excerpts needed for the approved task, redact secrets and unnecessary personal/contact data, avoid raw page bodies and credentials, and record a redacted request/policy fingerprint rather than a prompt containing sensitive data. Every generated suggestion must cite the tenant-scoped evidence IDs/source references and preserve evidence hash, citation, observed time, provider/model/version, policy version, and uncertainty. Hashes identify the exact evidence snapshot for reproducibility; they do not prove that the source is true. Missing, conflicting, stale, suppressed, or low-quality evidence must remain visible and must not be filled with invented facts.
AI output is an untrusted draft. It requires an authorized human approval/rejection (and an explicit reason for material changes) before it can become a stored claim, score input, pipeline update, contact decision, export, or any other consequential record. Approval must re-check tenant scope, suppression, evidence freshness, policy/version, and the unchanged evidence hash; stale or changed inputs require re-review. AI cannot create or edit CRM interactions/outcomes as if communication occurred, send messages, create campaigns, schedule follow-ups, contact prospects, merge records, acquire domains, or perform autonomous outreach. Suppressed/do-not-contact records remain visible for safety review and are never made eligible by an AI result.
Phase 13 is pilot-only. Before production, implement provider allowlisting and secret management, data-processing agreements, prompt/output redaction tests, evidence hash/citation verification, approval and rollback semantics, immutable audit coverage, tenant-isolation tests, retention/deletion/legal-hold jobs, cost/rate monitoring, incident disablement, and evaluation for hallucination, prompt injection, bias, and stale/conflicting evidence. See the API, web, security, and operations contracts for the authoritative limitations.
Phase 14 prepares outreach records without sending them. The platform may assemble a bounded, tenant-scoped draft from approved evidence, but it has no send endpoint, delivery worker, campaign scheduler, SMTP probe, validation message, or autonomous follow-up. `AUTOMATED_OUTREACH_ENABLED=false` remains the enforced Compose default and a draft is never a queued or delivered message.
A future outreach provider must be explicitly registered and allowlisted with an owner, capability/purpose, tenant and data-class scope, processing region/retention terms, model or API version where applicable, timeout and payload limits, per-tenant/global rate caps, daily cost/message caps, health/circuit policy, approval expiry, and operational enablement. Provider credentials belong only in a deployment secret manager; never place them in source, Compose, browser code, logs, audit payloads, or committed `.env` files. Provider failure, timeout, quota, circuit-open state, expired approval, or policy mismatch fails closed and must not silently switch to an unapproved fallback.
A draft is eligible only after server-side gates pass: the intended recipient and organization are tenant-scoped; the contact is not suppressed or do-not-contact; required evidence is present, cited, sufficiently fresh, and permitted for the stated purpose; consent/legal basis and jurisdiction/channel policy are recorded; the current score/eligibility state is not stale or uncertain; and a permitted human explicitly approves the exact draft. Public availability, a high score, a verified pipeline state, an extracted address, or an AI citation is not consent, legal basis, deliverability, or permission to contact. Suppression always wins and must be re-checked immediately before approval and any future send attempt.
Drafts must retain evidence citation IDs/source references, exact evidence snapshot hash, observed times, policy/provider versions, recipient/channel, redacted content fingerprint, actor, approval decision/reason, expiry, and correlation/idempotency key. The UI and API must display citations and uncertainty, preserve unsupported/conflicting evidence, and label the result **Draft only — human approval required**. Human approval is explicit, tenant-scoped, single-purpose, and bound to the unchanged draft/evidence hash; edits, stale evidence, policy changes, or approval expiry require a new review. Approval does not itself send or authorize a later send.
Any future side-effecting operation must require a tenant-scoped idempotency key bound to a request fingerprint and draft/provider/policy version. Exact replay returns the original bounded result; conflicting reuse is rejected. Enforce caps before provider calls and across retries, fallbacks, workers, and tenants, with bounded backoff and no cap bypass by rotation. Append audit events for draft creation, gate outcomes, citations, approval/rejection/expiry, provider attempts, cap denials, suppression decisions, retries, and any future send result; redact secrets, full prompts, credentials, and unnecessary contact content.
Phase 14 is preparation only and remains pilot-grade. The current Compose stack has no outreach provider, draft/send API, consent ledger, legal-policy engine, durable approval workflow, production secret manager, durable queue/worker, delivery telemetry, bounce/complaint handling, or compliance-grade audit/retention service. Before production, obtain jurisdiction-specific legal review and documented consent/legal-basis policy, implement provider contracts/DPA and secret isolation, durable transactional idempotency and audit, suppression synchronization, approval expiry/rollback, rate/cost controls, delivery feedback, incident kill switch, retention/deletion/legal-hold workflows, and end-to-end tenant-isolation and no-send tests.
See `docs/DEPLOYMENT.md` for Phase 15 portable backup/restore, readiness, rollback, monitoring, and systemd/Virtualmin operations. No deployment is performed by this repository change.
## Phase 15 production-readiness boundary
Phase 15 documents a portable deployment pattern for a Virtualmin-managed VPS; it does not claim that this repository provisions Virtualmin, TLS, DNS, backups, or a production database. The supported baseline is Docker Engine plus the Compose v2 plugin on a Linux host, with Virtualmin (or another reverse proxy) terminating HTTPS and forwarding only to the published web/API ports. The operator owns firewalling, DNS, certificates, Docker access, host patching, resource capacity, and an off-host backup destination.
Before deployment, verify the prerequisites in `docs/RELEASE_CHECKLIST.md`: a reviewed commit, Docker/Compose, DNS and TLS, a protected deployment directory, secret injection, backup destination, monitoring, and a tested rollback owner. Copy `.env.example` to an untracked `.env` only for non-secret defaults. Production secrets and bootstrap credentials must come from a secret manager or protected Virtualmin deployment environment; remove bootstrap values after first-run provisioning and never commit or print them.
The API liveness endpoint (`/api/v1/health/live`) and web `/healthz` are public process checks; API `/api/v1/health/ready` additionally verifies SQLite readiness. They are suitable for Docker and monitoring but do not prove backups, tenant authorization, or external dependencies. Production ingress must not route traffic until both Compose services report `healthy` and the deployment smoke tests pass. The current image initializes SQLite from `schema.sql` and has no standalone migration runner. Treat schema changes as a versioned, backup-first migration: validate on a restored copy, record the schema/data checks, and keep the previous image/config available for rollback.
The named Docker volume is not a backup. Stop or quiesce writes, create an encrypted off-host backup, verify it, and perform a restore drill before calling a deployment protected. Define retention for the database, audit/source lineage, logs, and backups; apply legal holds and deletion rules deliberately. Do not run `docker compose down -v` on a data-bearing host. Outbound traffic is deny-by-default for product behavior: `AUTOMATED_OUTREACH_ENABLED=false` is fixed in Compose and this release has no send/provider/worker path. Unexpected egress is an incident. SQLite, the in-process worker, HTTP-only local Compose, lack of durable migrations/PITR, and the limited SQLite-only readiness check are explicit limitations, not hidden guarantees.