# CONTRACT.md — Lily

Every clause is a product guarantee with a stable ID. Before an implementation is released, every clause needs a tagged test and `pnpm verify` must report it as satisfied, intentionally overridden, or broken. `BASELINE.md` contains cross-seed security, privacy, and operational guarantees; capacity and rate limits are data in `seed.json`. This Stage 1 document defines the 1.0 target. It does not claim that the current Worker already implements these clauses.

Clauses marked *Policy* name a buyer-replaceable product decision. A policy is an independently defined Lily decision, not a claim that research found a cross-incumbent convention. Its default remains available and testable when no buyer replacement is installed.

## Scope

Lily is a self-hostable ticket desk for one business. A requester, agent, or configured intake channel creates a numbered ticket. Agents work from shared and saved views, carry out the ticket's messages and notes, coordinate through groups and followers, apply macros and automations, and complete the ticket with an auditable outcome. Lily includes a public Help Centre, web form, and widget; email and voice can be enabled as channel adapters. It has customer records, analytics, service targets, and customer satisfaction feedback.

A ticket is a visible work item and case. Its customer-facing messages live on a linked conversation; its notes, evidence, assignments, actions, and lifecycle live on the case. Lily creates one hidden conversation for each newly created ticket by default. The ticket desk has no separately browsable conversation inventory. A conversation may link to more than one case where an explicit later product operation permits it.

Manual ticket work, including public replies when a channel is configured, is complete without AI, commerce, CRM, or payment adapters. The optional commerce-adapter specification in `research/optional-commerce-adapter.md` retains the former commerce requirements without making them Lily core behaviour.

## Home and application

- **HOME-001 — Deployment front door.** `GET /` presents the configured business name, a short configured description of its support service, links to the public Help Centre and configured public request surfaces, and a sign-in link to `/signin`. It contains no vendor pricing, trial, billing, product-switcher, or marketing claims.
- **APP-001 — Authenticated workspace.** An authorised user entering `/app` can reach Home, Views, Tickets, Customers, Help Centre, Analytics, Channels, and Administration according to their role. Navigation has a compact responsive form, global search, keyboard shortcuts for search/navigation/common ticket actions, contextual coach marks, and a dismissible setup checklist whose completed state is per Administrator. An unavailable optional channel or adapter is labelled unavailable with the configuration action rather than simulated data.

## People, roles, groups, and access

- **ACCESS-001 — Roles.** Lily has Administrator, Agent, Contributor, and Light Agent roles. Administrators manage deployment-wide settings, people, groups, forms, roles, channels, and policies. Agents work tickets and public replies. Contributors can make internal contributions only. Light Agents have the explicitly configured limited ticket access. An authenticated person may perform only actions permitted by their role and any ticket/group restriction.
- **ACCESS-002 — Groups and membership.** Administrators create, rename, retire, and populate groups. A ticket can be assigned to a group and then an agent; group assignment is visible in views, history, filters, reports, and exports. Retiring a group requires an explicit reassignment or unassignment of its open tickets and retains historical attribution.
- **ACCESS-003 — Authorisation and lifecycle.** Administrators invite, suspend, restore, and remove agents. Removing an agent unassigns their active tickets and preserves the author identity on earlier messages, notes, and audit records. The last active Administrator cannot be removed. Agents configure their own signature and notification preferences; the configured signature is appended only to their public email reply.
- **ACCESS-004 — Concurrent edits.** Each case mutation supplies the revision it was based on. A stale mutation returns `409` and makes no partial field, status, note, assignment, automation, or outbound-message change. An accepted inbound message is durable even if it races with an agent edit.

## Tickets and conversations

- **TKT-001 — Creation.** A human customer email to the support address or an agent's manual submission creates one ticket with an immutable ID, creation time, customer, channel, subject, initial message, declared ticket custom fields, and revision. A new ticket is Open with Normal priority unless the creating agent chooses another priority.
- **TKT-002 — States and pending reasons.** A ticket is Open, Pending, Solved, Closed, or Merged. Pending carries exactly one configured reason, and every reason declares whether it waits on the customer; a ticket is never Pending without a reason. Solved records an agent-confirmed outcome, Closed is immutable archival history, and Merged is an immutable redirect under TKT-016.
- **TKT-003 — Customer reply.** A qualifying human customer message on an Open, Pending, or Solved ticket appends to that ticket, makes it Open, and clears its pending reason. A message matching Closed creates a linked new ticket; a message matching Merged appends to the destination under TKT-016. No qualifying human customer message is discarded.
- **TKT-004 — Auto-close.** A Solved ticket closes seven days after it was last solved unless a qualifying human customer message arrives first. Closing never deletes messages, evidence, executions, or audit history. *Policy: `ticket.auto-close.v1`; default: seven days.*
- **TKT-005 — Queues and views.** Agents can filter tickets by status, pending reason, priority, assignee, group, tag, channel, service-target state, and ticket custom fields; sort by target due time, last activity, or priority; and save a named view that authorised agents can open. My queue lists Open and Pending tickets assigned to the current agent, earliest target due time first.
- **TKT-006 — Search.** Full-text search over ticket IDs, subjects, message bodies, customer names and email addresses, tags, and configured custom fields returns matching authorised tickets within 500ms at the stated limits.
- **TKT-007 — Assignment.** New tickets are assigned by the assignment policy. Agents may assign or reassign a ticket to one agent, one group, or both without changing state or service clocks. *Policy: `ticket.assignment.v1`; default: unassigned.*
- **TKT-008 — Priority.** Every ticket has Urgent, High, Normal, or Low priority. Authorised agents and allowed automation may change it; each change records actor, prior value, new value, and time.
- **TKT-009 — Timeline.** The ticket timeline presents customer messages, public replies, internal notes, system events, adapter evidence, automation runs, and execution records in durable event-time order, with insertion order as a stable tie-breaker across reload and pagination.
- **TKT-010 — Public and internal messages.** Public replies are eligible for customer delivery. Internal notes are a distinct stored kind that cannot be passed to a delivery adapter, included in customer-facing export, or used to claim an action completed.
- **TKT-011 — Solved guard.** Solving is an explicit agent action and sends no draft or external action. A Solved ticket rejects new public replies and external executions until it is reopened under TKT-003 or TKT-015; Closed and Merged tickets reject them permanently.
- **TKT-012 — Concurrent changes.** Each ticket mutation supplies its revision. A stale mutation is rejected with the current revision and makes no partial change; inbound message append never fails because of a concurrent agent edit.
- **TKT-013 — Ticket merge.** An agent may merge only tickets for the same customer, neither Closed nor Merged, after confirming source and destination IDs and a reason. The source becomes Merged and stores the destination ID. Cross-customer merge is refused; it needs a separately versioned, elevated extension with explicit participant and recipient reconciliation before customer-visible data could cross a customer boundary.
- **TKT-014 — Manual tickets.** An agent-created ticket records whether its initial message came from the requester or agent, and that attribution is immutable. Agent-originated tickets start no first-response clock and are excluded from first-response reporting.
- **TKT-015 — Operator reopen.** An agent may reopen Solved to Open with a required reason; it starts a new service cycle, sends nothing, and records the action. Closed and Merged tickets cannot be reopened.
- **TKT-016 — Merged history and replies.** A Merged ticket is read-only, its timeline is presented on the destination, replies addressed to stored threading identifiers reach the destination, and both IDs remain resolvable in search and audit history. Its source conversation stays linked to the immutable source and is transcluded on the destination with original conversation and author identity. Source and destination retain independent lifecycle and completion histories; routing or transclusion changes neither lifecycle.
- **TKT-017 — Tags.** Agents apply and remove case-insensitive canonical tags on tickets and customers; each change records actor and time. Tags filter queues, match search, serve as automation conditions and report breakdowns, and appear in export. Removing a tag retains the audit event.
- **TKT-018 — Reply outcome.** Sending a public reply leaves the ticket Pending with a configured reason that waits on the customer, unless the agent selects a different permitted status before sending. Sending never solves a ticket alone. *Policy: `ticket.reply-status.v1`; default: Pending, waiting on the customer.*
- **CASE-001 — Numbered case and default conversation.** A Lily case is the visible numbered ticket. Its public number is unique for the deployment life, never reused after deletion or merge, and remains resolvable after import. Each new ticket creates one hidden conversation by default. Customer-visible messages live on that conversation; notes, evidence, assignment, actions, and lifecycle live on the case. Completion never closes or deletes the conversation.
- **CASE-002 — View predicates and columns.** A saved view has an All block and an optional Any block. It matches when every All condition matches and, when Any is non-empty, at least one Any condition matches; an empty All block is true and an empty Any block imposes no additional condition. Rows, counts, exports, dashboard drill-through, sorting, pagination, and selected columns use the same stored predicate.
- **CASE-003 — Forms, followers, context, and work tabs.** Administrators define ticket forms, required fields, and custom fields; creation preserves the selected form and submitted values. An authorised agent can add/remove followers, who receive `ticket.followers.v1` updates without needing to be owner or assignee. Detail exposes requester context, selected columns, filtered activity types, and configured tabs. *Policy: `ticket.followers.v1`; default: assignment, requester message, note, public reply, at-risk, breach, and solved events notify followers subject to preferences.*
- **CASE-004 — Bulk and destructive work.** An authorised agent can apply reviewed bulk assignment, tag, status, macro, or spam actions to a filtered selection with independent per-ticket success, skip, or failure outcomes. Spam marking, requester suspension, deletion, and restoration require the separately authorised destructive action and preserve audit history under baseline retention.
- **CASE-005 — Agent and mobile work.** Home presents assigned/followed work, capacity, activity, and queue links; availability influences only later routing. The responsive mobile workspace supports ticket work and each agent can configure mobile notification categories and quiet hours with the same routing and suppression semantics as NOTIFY-001 and NOTIFY-002.
- **CASE-006 — Live conversation.** A configured widget may keep a live visitor/agent conversation. Each live conversation has a visible participant and queue state, is linked to its ticket when a request is submitted or an agent creates one, and retains its message chronology when it becomes the ticket's hidden conversation. An agent presence indicator never claims a live responder when none is assigned.
- **CASE-007 — Global and recent search.** Global search returns authorised tickets, customers, organisations, Help Centre articles, agents, groups, and saved views, identifies the matched type and field, and keeps a per-agent recent-record list that can be cleared. Ticket full-text latency remains TKT-006; global results preserve access checks and do not reveal a recent record after access changes.
- **CASE-008 — Work affordances.** Agents can open concurrent ticket tabs without losing unsaved drafts, return to a chosen tab, and see its current revision or conflict state. A queue can offer an explicit guided next-ticket action based on its visible ordering; its action, selection rationale, and any skipped ticket are recorded. The exact guided-play interaction is a Lily decision because the available Zendesk capture establishes the surface but not its execution.
- **CASE-009 — Tag entry.** Ticket tags display as removable chips, offer existing canonical tags while typing, and permit an authorised agent to create a new tag inline. Each entry/removal is audited. Autocomplete and inline creation are Lily product requirements; the cited Zendesk evidence establishes display and removal only.
- **CASE-010 — Rich composition, counters, and capacity.** The composer persists rich formatting, emoji, links, and accepted attachments before submit. Workspace chrome shows authorised agents live conversation and assigned-work counters. Administrators set an enforced per-agent active-ticket cap; routing skips capped agents and records the reason, while direct assignment above the cap requires an explicit authorised override.

## Email and other channels

- **MAIL-001 — Inbound email.** A signature-verified provider delivery to a configured support address creates or appends a ticket message within 60 seconds, preserving sender, recipients, subject, sanitised text and HTML, attachments, provider ID, and threading headers.
- **MAIL-002 — Threading.** An inbound email appends only when `In-Reply-To` or `References` matches a stored message ID, or a valid Lily-issued opaque reply token identifies its ticket, **and** its sender is the current requester or current participant. The timeline audits which identifier matched. A removed or unknown sender starts a new ticket even with a valid identifier; sender and subject alone never join mail.
- **MAIL-003 — Threading conflict.** An unmatched message starts a new ticket. Conflicting headers, or a valid token that disagrees with them, start a new ticket with a visible conflict event naming both candidate tickets; the message is never attached speculatively.
- **MAIL-004 — Inbound idempotency.** Replaying one provider event with the same provider message ID produces one stored message, one service-cycle effect, and at most one routing, acknowledgement, and automation effect. Provider-event ID and RFC `Message-ID` are durably one-to-one. If either known identity arrives paired with a new, conflicting identity or immutable thread metadata, Lily quarantines the conflict without routing, service-cycle, acknowledgement, or automation effect until reviewed. Body equality never resolves it.
- **MAIL-005 — Automatic mail.** Mail with automatic, bulk, delivery-status, receipt, or no-reply signals is non-actionable. A matching item is stored as a visible system event and does not reopen, notify, start a service cycle, run automation, or request an acknowledgement. An unmatched item creates no queue entry and is retained as a discarded-mail audit record.
- **MAIL-006 — Outbound email.** A public reply atomically creates one durable outgoing message and one dispatch job with a stable idempotency key, reply headers, and a Lily-issued opaque reply token, then dispatches that job within 60 seconds. It uses the ticket's stored reply mailbox: the first configured support-address match retained from RFC intake under MAIL-011, or, for a manual, web-form, widget, or other non-RFC ticket, exactly one enabled configured support mailbox selected and stored by an authorised agent before its first public reply. A repeated key returns the original message and dispatch job.
- **MAIL-007 — Delivery state.** Each outgoing message monotonically progresses through queued, accepted, delayed, delivered, bounced, rejected, suppressed, failed, or needs_review. Provider acceptance is never customer delivery. There are at most five total dispatch attempts in one hour with the original key. A failure before provider acceptance never stops a first-response clock. Lily preserves provider-acceptance time and delivery evidence. If the sole qualifying public reply later reaches final bounced, rejected, or failed state, it restores that service cycle's original first-response clock and target due calculation without resetting elapsed time; if another qualifying public reply remains provider-accepted, the clock stays stopped. The pre-send ticket status and its mutation revision are recorded; on that same sole-reply final failure, Lily restores the pre-send status only when no later independent status mutation occurred, never clobbering later agent changes. Final failure or needs_review notifies under NOTIFY-004. An ambiguous needs_review outcome is never automatically retried and requires provider reconciliation.
- **MAIL-008 — Authoritative delivery reports.** Verified provider reports move an accepted message only forward to delivered, delayed, bounced, or rejected and record report time. Duplicate or out-of-order reports never regress a terminal state; Lily never labels delivery without an authoritative report.
- **MAIL-009 — Attachments.** Inbound and outbound attachments remain bound to their immutable message. A file outside the seed limits is rejected with filename and reason while the readable message and accepted files are retained.
- **MAIL-010 — Provider events.** Intake and delivery webhooks are rejected without a valid `mail.receiver.v1` signature. Duplicate and out-of-order delivery events are tolerated and never create data or regress a terminal state.
- **MAIL-011 — Recipients and support-address intake.** One RFC message addressed to multiple configured support addresses creates one intake record, retains every matched support address, and records the first configured match as its reply mailbox. A new ticket records the sender as requester and only non-support `To` and `Cc` recipients as participants; Bcc is never disclosed. A public reply addresses the requester and current participants the agent keeps in the composer; removed recipients are not restored later. Support and notification addresses are never participants.
- **MAIL-012 — Intake durability and destination.** Lily acknowledges a verified intake only after a durable intake record exists; failure before it returns `503` with no partial ticket. Post-record parse, storage, or enqueue failure is visible with a retryable or terminal reason. A verified message with no configured support destination is quarantined with destination and reason.
- **MAIL-013 — Mailbox identity.** An agent-authored public reply uses the ticket's stored reply mailbox: the first configured RFC support-address match under MAIL-011, or the authorised enabled configured-mailbox selection stored under MAIL-006 for a manual, web-form, widget, or other non-RFC ticket. It carries ticket threading data and appends that agent's configured signature. Operator notifications use a no-reply address that is not a support destination and carry no agent signature. Automatic acknowledgement is not a notification and follows MAIL-014.
- **MAIL-014 — Automatic acknowledgement.** When enabled, the actionable message that creates a ticket produces exactly one stored automatic acknowledgement under MAIL-006 through MAIL-008. It uses the ticket's stored reply mailbox and threading data but no agent signature; it is not sent from the no-reply notification address. No acknowledgement is sent for manual, non-actionable, blocked, or later ticket messages, and it never counts as a first response. *Policy: `channel.autoresponder.v1`; default: disabled until an Administrator enables it for a mailbox.*
- **CHAN-001 — Channel catalogue.** Administrators see every configured intake and delivery channel, its enabled state, form or mailbox binding, group routing, public address, and configuration health. Disabling a channel preserves its history and stops only later intake or dispatch through that channel.
- **CHAN-002 — Web widget and simulator.** A configured public widget collects a request with the configured form and creates a ticket under TKT-001. It identifies the current requester where consented, exposes the required privacy text, and confirms a successful request without pretending an agent is present. An Administrator can use a simulator to preview configured form, bot, appearance, proactive-message, and live-conversation states without creating a real ticket or sending a visitor message.
- **CHAN-003 — Grounded bot assistance.** A configured bot greets the visitor, offers up to ten article-grounded generated answers from approved Help Centre material, and shows cited article links. It stores every bot and visitor turn before agent handoff in the linked conversation, collects the ticket form, and hands off for no valid answer or visitor request. Core `ai.provider.v1` receives only approved article material and disclosed visitor input, never private notes; cited identifiers are validated against supplied articles and invalid output fails for review rather than falling back to invented copy.
- **CHAN-004 — Proactive messages.** An administrator can configure an eligible widget audience and a reviewed proactive message. The configuration states its trigger and frequency limit, respects consent and suppression, and records each presentation; it does not create a ticket unless the visitor submits a request.
- **CHAN-005 — Voice queue and ticket link.** A configured voice adapter exposes queues or lines with waiting-call count, agent or group routing context, call state, and stable Ticket ID when one exists. An authorised agent can attach a call record to an existing ticket; the attachment is audited and adds no invented call-to-ticket relation when no ticket was selected. This is a Lily lifecycle decision; the Zendesk evidence only establishes a Live Calls Ticket ID column.
- **CHAN-006 — Side conversations.** An authorised agent can open a labelled side conversation with an approved external participant, see its delivery state and replies on the ticket, and keep it separate from the customer-visible conversation. It cannot disclose private notes without an explicit reviewed message.

## Customers, organisations, and record links

- **CUST-001 — Identity.** The first qualifying customer message from an unseen normalised email address creates one customer; comparison is case-insensitive and a shared display name never merges identities.
- **CUST-002 — Profile.** A profile has display name, primary and observed email addresses, locale, tags, declared custom fields, and private account notes. Locally owned fields are editable and adapter-sourced fields are read-only with source and refresh time.
- **CUST-003 — History.** A profile shows every Open, Pending, Solved, Closed, and Merged ticket for that customer with its satisfaction and available adapter records.
- **CUST-004 — Notes.** Agents may add private, attributed customer notes. Notes are never sent to the customer or written back through an adapter.
- **CUST-005 — Merge.** An agent merges customers only after confirming survivor and source IDs and seeing addresses, ticket counts, and record links. Tickets, addresses, notes, tags, fields, and audit history move to the survivor; incompatible provider customer IDs block the operation and name the conflict.
- **CUST-006 — Erasure boundary.** Erasing a customer removes locally owned messages, attachments, notes, evidence snapshots, and tickets under `BASE-DATA-003`; it never claims to erase a provider account or provider data.
- **CUST-007 — Resync suppression.** This predecessor guarantee applies only to an enabled `external.sync.v1` extension described in `research/optional-commerce-adapter.md`: erasure stores a one-way keyed digest of normalised provider namespace and resource ID, no customer field, to prevent automatic resync recreation; it travels with export/import and only explicit restore removes it. Core Lily has no automatic external sync or sync credential.
- **CUSTOMER-001 — Organisations and suspension.** Administrators create organisations and membership. An authorised agent may suspend a requester; *Policy: `requester.suspension.v1`; default: new intake is quarantined for review while earlier history stays readable.*
- **CUSTOMER-002 — Imports and record links.** An Administrator can run a reviewed CSV customer import with mapping, validation, duplicate handling, and per-row outcomes. An optional record-link adapter attaches named external records with adapter, identifier, link state, and retrieval time; unavailable data is labelled unavailable and never fabricated.

## Help Centre

- **HELP-001 — Public Help Centre and audiences.** An Administrator activates or deactivates the public Help Centre; deactivation makes public article and request paths unavailable while preserving authoring history. Published content is visible only to its named audience. An audience selects signed-in users, staff, groups, tags, and users with All/Any blocks: all All conditions and, when non-empty, one Any condition must match; empty All is true and empty Any adds no restriction. Administrators preview matching users and anonymous/staff roles. A preview is not evidence of signed-out deployment reachability.
- **HELP-002 — Articles, taxonomy, history, and bulk work.** Agents with article authority can create, edit, publish, unpublish, archive, and restore articles in categories and sections. Articles have a title, body, author, timestamps, lifecycle state, audience visibility, content tags, and version history. Article history is filterable by actor, event, and date. A selected article set supports reviewed bulk publish, unpublish, archive, restore, category, and audience changes with per-item success, skip, or failure results.
- **HELP-003 — Findability and media.** The Help Centre offers text search, category navigation, pagination, article metadata, a shared media library with upload, search, insert, and removal permissions, and article comments when comments are enabled. Search results respect audience visibility and label unavailable translations or attachments rather than substituting a different article.
- **HELP-004 — Languages and appearance.** Administrators configure Help Centre languages, translated article variants, branding, theme appearance, and a reviewed public preview. A language fallback is explicit and never represents untranslated copy as a translation.
- **HELP-005 — Help Centre requests.** A public request form uses an administrator-selected ticket form, validates required fields, creates a ticket under TKT-001, and gives the requester a result that does not expose another ticket. Its submission is distinguishable from article comments and widget intake.

## Administration, macros, service targets, and automation

- **ADMIN-001 — Operational settings.** Administrators configure business hours, IANA timezone, holiday calendar, ticket types and priorities, statuses, forms, custom fields, canned replies, mailbox identities, routing, authentication, retention, and API/webhook credentials. Retention configuration can label, report, or schedule review of records but cannot automatically delete user data; deletion follows the applicable baseline clause and explicit authorised action. The settings screen distinguishes active configuration from unavailable optional adapters.
- **ADMIN-002 — Business rules, capacity, and usage.** Administrators can inspect routing rules, capacity limits, API/webhook integrations, installed approved applications, their last execution or error, and current storage/API/channel usage meters with source, period, and refresh time. No setting silently expands authorisation or gives an external application access beyond its configured scope.
- **MACRO-001 — Macro library.** Authorised agents create, edit, archive, and search shared macros for a public reply or internal note. A macro may propose tags, priority, assignment, form fields, or status, but does not itself send a message. The library reports applied count, recent use, and sortable rolling seven-day usage without exposing ticket content to an unauthorised viewer.
- **MACRO-002 — Placeholders.** Applying a macro resolves its declared placeholders from the current ticket, requester, organisation, and agent. An unresolved required placeholder blocks use and identifies the missing value.
- **MACRO-003 — Review boundary.** A macro fills an editable draft and previews every field change. Submitting remains an explicit TKT-018 and MAIL-006 action and cannot bypass revision, authorisation, or outbound delivery rules.
- **SLA-001 — Calendar and selection.** Administrators configure first-response and resolution targets against one workspace timezone, working-hours schedule, and holiday calendar. *Policy: `service-target.selection.v1`; default: targets are selected by ticket priority and measured in business time.* A ticket with no matching target displays that fact and is excluded from met/breached denominators.
- **SLA-002 — Snapshot and calculation.** Starting a service cycle stores the target version, duration, due time, and business-calendar inputs. Later setting edits do not rewrite its result. Recalculation records its reason and prior due time.
- **SLA-003 — First response.** The first-response clock stops only when the first public agent reply is provider-accepted, subject to MAIL-007's sole-qualifying-reply final-failure restoration. Assignment, notes, macros, automation, an acknowledgement, and queued or failed delivery do not stop it.
- **SLA-004 — Resolution cycle.** The resolution clock runs for Open, and for Pending unless the selected pending reason explicitly waits on the requester. It stops when the case is solved. A requester message or agent reopen starts the next cycle while retaining prior cycles.
- **SLA-005 — Visibility.** Ticket rows, ticket detail, Home, and saved views show the exact due time, calendar basis, remaining or overdue time, and state `active`, `paused`, `at_risk`, or `breached`. At risk begins at 25% of target duration remaining in business time.
- **SLA-006 — Breach effects.** Crossing a target marks the cycle breached, emits a notification event, and never resolves, closes, reassigns, or otherwise mutates the ticket on its own.
- **QUEUE-001 — Filter-exact dashboard.** Home shows the current selected view's open backlog, urgent count, target-state counts, resolved-today count, mean first-response time over the last 24 hours, and satisfaction score over the last 7 days. For every ticket state it shows the count and longest current wait. Every count opens the equivalent saved-view conditions and shows the data timestamp; it is no more than 30 seconds old at the stated limits.
- **QUEUE-002 — Target tiers.** An active ticket with a target is exactly one of `breached` once past due, `at_risk` with 25% or less of its target duration remaining, `due_soon` with more than 25% remaining and two business hours or less to due, or `later` otherwise. A paused cycle is `paused`; a ticket with no matching target is `no_target`. The four active-target tiers are mutually exclusive and exhaustive.
- **QUEUE-003 — Operational ordering.** The default work ordering is breached, at_risk, due_soon, later, paused, no_target, then nearest active due time, priority, latest requester activity, and ticket number. An agent may choose documented alternatives without changing the selected view conditions.
- **QUEUE-004 — Due-state detail.** A queue and ticket disclose which target is due, absolute due time with timezone, and the previous/current due time and reason for a recalculation.
- **QUEUE-005 — Measured times.** First-response and resolution measures are stored as business and calendar elapsed time and shown with their cycle. Reopening never overwrites a recorded first response.
- **AUTO-001 — Rule lifecycle.** A rule is Draft, Active, or Paused and has a name, ordered conditions and actions, creator, latest editor, version, and run count. Draft and Paused rules process no new events.
- **AUTO-002 — Allowed actions.** A rule can react to ticket creation, requester message, ticket field or status change, or one scheduled elapsed-time condition. It may apply a tag, set priority, assign an eligible group or agent, set a pending reason, send an internal notification, or apply a macro draft. It cannot send a public reply, complete a ticket, change a requester record, or invoke an external mutation. *Policy: `automation.action-allowlist.v1`; default: the listed actions only.*
- **AUTO-003 — Order and audit.** Active rules evaluate in visible order against one immutable triggering revision. Each run records matching conditions and each applied, skipped, or failed action.
- **AUTO-004 — Idempotency and loops.** A rule run is unique by rule version, ticket, triggering event or scheduled window, and action. An evaluation chain evaluates each active rule version at most once; duplicate events and scheduler runs repeat no effects.
- **AUTO-005 — Concurrent changes and failure.** A rule rechecks its conditions and ticket revision before mutation. A conflict is a visible skipped run. A failed action is visible with rule, ticket, attempted action, and error; a retry reuses its idempotency key.
- **AUTO-006 — Rule authorisation.** Only an Administrator may activate, pause, or change a rule's owner-visible scope. A rule cannot act on a ticket, group, form, or field its owner is not authorised to configure.
- **AUTO-007 — Reviewed recovery.** A retried failed rule run uses the original triggering revision and idempotency keys, reports any now-stale condition, and never replays an action that already succeeded.

## Notifications, satisfaction, and analytics

- **NOTIFY-001 — Accountable ticket updates.** New unassigned tickets notify every eligible Agent; direct assignment notifies the new assignee or every eligible member of the assigned group; self-assignment sends nothing. Requester follow-up, at-risk, breach, and solved notifications go to followers, or to the assignee then eligible Administrators if there are no followers. An at-risk and a breach notification occur at most once each per service cycle.
- **NOTIFY-002 — Coalescing and quiet hours.** Repeated requester-follow-up events coalesce to one notification until an assigned agent opens the ticket; repeated event categories use their named service-cycle or open-cycle boundary. *Policy: `ticket.quiet-hours.v1`; default: quiet hours defer non-urgent notifications while urgent and breached work bypasses them.* Intake, service clocks, and other recipients are unaffected.
- **NOTIFY-003 — No mail loops.** Notifications use a configured no-reply sender that is not a support destination. Mail to that address creates or reopens no ticket and triggers no automation. Automatic acknowledgements are ticket mail under MAIL-014, not notifications.
- **NOTIFY-004 — Failure reaches a person.** A public-message delivery failure, automation failure, notification failure, or configuration/security failure is visible on the ticket or settings record and is delivered to the initiating agent, or every active Administrator when there is no initiating agent. This responsible-failure notification cannot be muted and never blocks or rolls back its causing event.
- **CSAT-001 — Survey request.** A solved ticket with a customer-visible accepted reply can queue one signed satisfaction request for that solve cycle. *Policy: `csat.request.v1`; default: one email 24 hours after the solve.* No survey is sent to a blocked requester, and reopening cancels an unsent request.
- **CSAT-002 — Survey response.** The single-use public link accepts one whole-number rating from one to five with an optional comment, expires after 30 days, exposes no unrelated ticket data, and never creates or reopens a ticket.
- **CSAT-003 — Survey history.** A result shows on the ticket and customer record with its solve cycle and time. A later cycle makes a new request and never overwrites a prior result.
- **INSIGHT-001 — Operational dashboards.** Analytics provides prebuilt dashboards for ticket volume and status, first-response and resolution duration, service-target outcomes, satisfaction score and response rate, assignment and group workload, and channel mix.
- **INSIGHT-002 — Dataset and filters.** Every report states its period, timezone, selected view or filters, ticket states, denominator, freshness time, and comparison period. It can filter by assignee, group, type, priority, tag, form, channel, custom field, and target state.
- **INSIGHT-003 — Report builder.** An authorised user can create, edit, save, share, and retire a report using documented ticket, customer, satisfaction, and service-target fields. A report exposes the tickets behind an aggregate only to viewers authorised for those tickets.
- **INSIGHT-004 — Formatting and export.** Dashboard and report users can choose supported chart and table formatting. An export contains the complete durable result for current filters and period with metric definitions, rather than only the rendered page.
- **INSIGHT-005 — Freshness and correctness.** Ticket, service-target, and satisfaction analytics reflect committed mutations within five minutes at the stated limits. A panel shows its last successful refresh and a stale or failed state; no cached result is represented as current.
- **INSIGHT-006 — Operational export.** An agent can export the complete durable current result of an authorised view, queue, dashboard, or report, including selected filters, period, and metric definitions. This convenience export does not replace DATA-001.
- **INSIGHT-007 — Merge accounting.** A merged source remains auditable and searchable but is excluded from live backlog, created, solved, and service-target totals. Its source conversation entries remain visible through canonical transclusion without double-counting the canonical case.
- **INSIGHT-008 — Explainable drivers.** Each classified volume, satisfaction, or service-target aggregate exposes its contributing tag or configured classification, stated denominator, and authorised ticket drill-down. Unclassified eligible tickets appear as `Other`; the displayed drivers, Other, and excluded denominator reconcile to the reported total.

## Data and import

- **DATA-001 — Portable export.** Lily's complete export includes tickets, cases, conversations, messages, attachments, customers, organisations, forms, tags, views, macros, service cycles, automations and runs, satisfaction records, settings, article versions, record links, notifications, and audit entries, with original attachments. It contains no secret and follows baseline data guarantees.
- **DATA-002 — Data boundaries.** Export, retention, deletion, and erasure identify locally owned data and optional-adapter references. Lily does not claim to update, delete, export, or erase a provider record unless the configured adapter returned a confirmed result for that operation.
- **DATA-003 — Zendesk importer.** An Administrator can use the documented importer with an administrator-provided Zendesk export into an empty Lily installation. It validates mappings before mutation and offers dry run. It imports numbered tickets, requesters, messages, original attachments, tags, forms, groups, users, status history, merge history, and available metadata, with a per-record outcome and one import audit record. Each source object has a preserved source ID and a Lily ID; source ticket numbers are retained only when unused, otherwise Lily allocates the next permanent number and records the source-number mapping. Existing source IDs make reruns idempotent. Imported merge and status history are retained as source history; unsupported data receives a per-record outcome without fabrication. Stage 2 must prove this with a real export; no Freshdesk importer is committed.

## Out of scope

The following are declared non-goals in `seed.json` and are not 1.0 guarantees: Zendesk community, badges, moderation, theme marketplace, dashboard sharing or restrictions, CRM suite/tasks/prospecting, native mobile SDK, social connectors, application marketplace, classic report builder, vendor benchmark survey, product switcher, signup/trial/billing chrome, workforce management, separately browsable conversations, multiple businesses or tenants, outbound campaigns, arbitrary external webhooks, native mobile apps, and an owned CRM, commerce, order, payment, fulfilment, or marketing system. Freshdesk replacement and import remain deferred until a compliant mapped public-source research record exists.
