From 8d9acc465830bc9cc163d1bff41fc5ec9cf7e2d7 Mon Sep 17 00:00:00 2001 From: TJ Baker <1617679+zaridan@users.noreply.github.com> Date: Thu, 23 Jul 2026 19:54:15 -0700 Subject: [PATCH] Establish support infrastructure charter --- CHARTER.md | 222 +++++-- CLAUDE.md | 16 +- CONTRIBUTING.md | 2 +- GOVERNANCE.md | 41 ++ README.md | 39 +- STATUS.md | 69 +- docs/architecture/README.md | 64 ++ docs/decisions/README.md | 72 ++ docs/history/CHARTER-v1.md | 96 +++ docs/history/README.md | 36 + legal/README.md | 51 ++ legal/module-api-exception.md | 8 +- legal/module-commercial-license.md | 146 ++-- legal/provenance-policy.md | 35 +- legal/trademark-policy.md | 6 +- package.json | 2 +- specs/api/agent-inbox-v1.md | 160 ++--- specs/auth/agents-and-auth.md | 123 ++-- specs/auth/passkeys.md | 103 ++- specs/deploy/gmail-inbound-runbook.md | 67 +- specs/mail/gmail-connect.md | 106 +-- specs/mail/gmail-push.md | 69 +- specs/mail/inbound-ingestion.md | 52 +- specs/mail/mailbox-connection.md | 24 +- specs/mail/sending.md | 38 +- specs/mail/threading.md | 24 +- specs/modules/catalog.md | 123 ++-- specs/modules/marketplace-v1.md | 913 +++++--------------------- specs/modules/substrate-v1.md | 29 +- specs/store/conversations.md | 8 +- specs/ui/admin-ia.md | 81 +-- src/providers/README.md | 2 +- web/README.md | 26 +- 33 files changed, 1331 insertions(+), 1522 deletions(-) create mode 100644 GOVERNANCE.md create mode 100644 docs/architecture/README.md create mode 100644 docs/decisions/README.md create mode 100644 docs/history/CHARTER-v1.md create mode 100644 docs/history/README.md create mode 100644 legal/README.md diff --git a/CHARTER.md b/CHARTER.md index 98cc24a..62eebfb 100644 --- a/CHARTER.md +++ b/CHARTER.md @@ -1,117 +1,213 @@ # Helpthread — Founding Charter -> Helpthread is an open-source, serverless helpdesk — shared inbox, threaded email conversations, knowledge base — built for teams who live on Vercel and Supabase rather than a LAMP server. It is aimed at the bar Help Scout set for ease of use — the experience this project exists to make ownable — rebuilt in modern TypeScript with no daemons, no long-running processes, and an extension system designed for how serverless software actually deploys. FreeScout, the established self-hosted PHP helpdesk, proved the self-hosted market is real; Helpthread is built for that market on modern rails. Core is AGPL-3.0; the project is dogfooded first, built by the team behind Resonant IQ as their own production support system before anyone else touches it. +> **Helpthread is open-source support infrastructure.** +> +> It exists so organizations can own, extend, and operate the systems through which they support their customers. -## 1. Mission & positioning +## First Principles -Helpthread starts from a personal fact: its founder ran customer support on Help Scout for years and loved it. Help Scout remains the gold standard for a helpdesk that feels effortless on both sides of the conversation — and that experience is the bar this project holds itself to. What Help Scout never offered is ownership: your helpdesk, your data, your customization, on your own infrastructure, with no rent forever. Help Scout's ease, fully owned — that combination is the product. +### 1. The operator owns the system -The self-hosted market is real: thousands of teams run FreeScout instead of paying rent to Zendesk or Help Scout. We ran it ourselves — and hit its walls. It's a decade-old Laravel 5.5 app that needs a persistent host running scheduler daemons, its extensibility means dropping PHP into a runtime plugin folder, and even basic customization — the knowledge base's design, its URLs — is out of reach without forking. Proof of the demand, and proof of the ceiling. FreeScout serves one further purpose here: because Help Scout is closed SaaS you can't inspect, FreeScout is our open *window* into that experience — a self-hostable reference for modeling the interface toward Help Scout's ease of use. It is a UX reference, never a source of code. The core is our own, built on modern, permissively-licensed foundations. +Organizations should be free to own, operate, and extend the infrastructure through which they support their customers. -Helpthread is the answer for people who already live on serverless infrastructure. Same ownership promise — rebuilt for a world of edge functions, managed Postgres, and push-based delivery where providers offer it, bounded scheduled fetches where they don't — and no resident process either way. Two audiences have to come out of this feeling the same way: customers should find getting support dead easy, and operators should find running the thing dead easy. Neither of those is negotiable in favor of the other. +Operators decide where Helpthread runs, where its data lives, which providers it uses, which extensions it trusts, and when it changes. They should be able to inspect, extend, and replace parts of the stack without losing the conversations entrusted to it. -## 2. Product principles +Ownership includes control over execution, configuration, integrations, customization, and upgrade timing. Changes to Helpthread must preserve meaningful operator choice. -- **Innovate on the platform. Be boringly faithful on mail semantics.** Everything about *how* Helpthread runs — compute model, storage, deployment — is fair game for rethinking from scratch. How it parses, threads, and sends email is not. Mature systems — FreeScout and Chatwoot among them — have earned a decade of edge-case scars in production email handling; we respect that earned knowledge, reproduce proven behavior, and verify against fixtures rather than re-deriving from first principles. Any change to mail behavior needs proof of equivalence or an explicit, written justification — not a hunch that the old way looked wrong. During this project's own early development we watched well-intentioned "improvements" to mail handling silently destroy message content in testing; that lesson is why this rule exists and why it's not up for debate per-PR. -- **Threading authority lives on the outbound side.** Inbound threading headers (`References`, `In-Reply-To`) are written by every mail client on earth, inconsistently — they cannot be trusted. The one header the engine fully controls is the Message-ID it emits. So threading is anchored there: signed reply tokens in outbound Message-IDs, authenticating replies on their way back in — a pattern as old as mailing-list software. This is the mechanism the whole system leans on; treat it accordingly. -- **API-first — and the UI isn't the only client.** Anything the UI can do, a typed public API can do. The UI is a client of that API, not a special case; so are AI agents. An MCP server exposing the same operations ships as a first-class client of the same API, so any operator can point their own AI tooling at their own helpdesk. -- **Serverless-native, Vercel-first, not Vercel-only.** No daemons, no long-running processes. Inbound mail arrives either by push webhook (Gmail push through Pub/Sub, and equivalents as we add providers) or by a bounded, stateless scheduled fetch — a function that connects, reads from a stored cursor, and exits. **Neither is "the primary" — the operator chooses at setup, trading latency against setup cost:** push is near-instant but needs a GCP project and roughly six provisioning steps (`specs/deploy/gmail-inbound-runbook.md` Part A); scheduled fetch costs up to a cron interval of latency and needs an app password or OAuth. Both are fully supported intake paths; neither is a fallback for the other. Nothing stays resident either way: no held connections, no IMAP IDLE, no worker loops. Anything that needs to happen later is a scheduled action or a cron trigger, not a process sitting in a loop. The engine's core reaches every platform service through interfaces the project owns — see the platform posture note in the architecture section. *(Amended 2026-07-20 — scheduled-fetch intake; see §7 appendix.)* -- **Own your data.** Self-hosted on the operator's own Vercel and Supabase accounts. Conversation data never touches Resonant IQ-operated infrastructure — we don't sit in the data path — and AI features preserve that: assistants call model providers the operator configures, with the operator's own keys. **The promise is absolute and carries no exception.** *(The 2026-07-19 "managed hosting" carve-out was **rescinded 2026-07-20** on TJ's review — he had selected managed hosting from a picker that labelled it "Recommended", and rejected the model once its consequences were clear. See the §7 appendix.)* +### 2. Conversations are the source of truth -## 3. Licensing & intellectual property +Support begins with a conversation between an organization and its customer. -Helpthread's core is licensed **AGPL-3.0**. Contributions are accepted under the **Developer Certificate of Origin (DCO)** — a sign-off on every commit, inbound license identical to outbound, no CLA, no copyright assignment. Contributors keep the copyright on their work, which makes the core's license permanent by structure rather than by promise. What makes it lawful to sell first-party commercial modules alongside the AGPL core is not ownership of the core — it is the module boundary and its plugin exception, defined below. Paid first-party modules ship under a separate proprietary/commercial license from their own closed repositories, distributed through an official marketplace — live at public launch, proven first as the project's own dogfood install path (amended 2026-07-19; see below). +Tickets, assignments, statuses, workflows, summaries, and reports organize or interpret that conversation. They do not replace it. Helpthread preserves the original record—its participants, provenance, sequence, and history—so future applications can interpret it without obscuring what occurred. -**Given up, deliberately:** dual-licensing the core — selling AGPL exceptions or relicensing later — is off the table once outside contributions land; no one can relicense the core without the consent of every contributor. That option is worth little here: the demonstrated buyers of AGPL exceptions *embed* a component in their own product, and Helpthread is an end application its users operate (running an unmodified AGPL helpdesk carries no copyleft obligations at all) — what remains, white-label resellers and blanket-AGPL-ban enterprises, is small. Also given up: a CLA's patent grant, warranties, and employer sign-offs — AGPL §11's per-contributor patent grant partially substitutes, and any indemnity offered to module customers is priced without upstream contributor representations. The revenue model is modules and the marketplace, not license arbitrage. The same structure binds Resonant IQ symmetrically: the company is a licensee of its contributors' code like anyone else, so core modifications running in any hosted offering get source-offered to its users under AGPL §13. +Communication correctness is therefore a core responsibility. -**What's free and what's paid, stated early:** the core helpdesk — mail engine, conversations, agent inbox, the public API and MCP server, self-hosting — is AGPL-licensed free software, forever, and nothing free today gets paywalled retroactively. Paid, eventually: advanced first-party modules (AI assistants are the leading candidates, and the knowledge base is now one too) and possibly hosted convenience services. Monetization adds; it never subtracts. +### 3. Infrastructure outlives applications -### Module boundary +Interfaces, workflows, channels, providers, and AI systems will change. The underlying records and contracts should remain dependable. -The line between the AGPL core and commercial modules must be an engineered artifact, not an accident. Modules — first- or third-party — integrate through a narrowly-defined, documented public plugin API, and the core's license will carry an explicit plugin exception for it (an AGPL-3.0 §7 additional permission in the Classpath-exception tradition), so module authors are never left guessing whether their work becomes a derivative of the core. Repository separation alone does no legal work — a build-time npm module compiles into the same running program as the core — so the exception text is the mechanism, and it carries the load for everyone: it covers network use (AGPL §13), not just conveyance, and it is deliberately symmetric — the same permission for first-party, third-party, and fork-based modules alike. First-party modules hold no privileged position: any hook a paid module needs ships in the public plugin API. Where a module can live out-of-process — webhooks, the event API — that is the preferred shape; it needs no exception at all. +Helpthread invests first in durable primitives: conversations, participants, identities, messages, events, routing, storage, and public contracts. Interfaces and optional capabilities build upon those primitives through the same platform available to everyone. -Under DCO the exception text is this project's real one-way door: once outside contributions merge, broadening it requires the consent of every copyright holder. Drawing the boundary precisely is therefore counsel-review work **before the first external contribution is accepted** — not before the marketplace opens — including how the exception text may evolve as the plugin API matures without ever becoming a back-door relicense of contributed code. The exception travels with every conveyed copy; a fork may keep it and grow its own module ecosystem, or may remove it as AGPL §7 permits — either way that is the accepted price and structure of the trust model, and it is why the assets that stay with the company — the Helpthread name and marks, the npm organization, the official marketplace — matter commercially. The discipline the boundary demands — narrow, stable, documented extension points — is the same discipline a good module API needs anyway. +## Project Commitments -### Provenance +These commitments turn the first principles into practical product and engineering constraints. -Helpthread is an independent implementation. The repository contains no code copied or derived from any copyleft-licensed project. The core is our own code, built on permissively-licensed foundations (RFCs for mail semantics, and MIT/Apache libraries and references such as postal-mime and Chatwoot's MIT core), with each license verified at adoption. Behavior is specified and validated against public standards, public documentation, and black-box observation of running systems. Every substantive change receives real human design and review before merge — ordinary pull-request review, preserved in git history — which is also what keeps AI-assisted work firmly copyrightable under current U.S. Copyright Office guidance. Copyright we can prove we hold is what gives the project standing to enforce the AGPL — copyright claims belong to whoever owns the lines at issue — and clean title to the commercial modules. +### Operator ownership -**Ownership, decided:** Resonant IQ, Inc. holds the copyright on its own work — pre-launch, that is the entire tree — owns the Helpthread name and marks, and will run the marketplace; Helpthread is a Resonant IQ product. Contributors keep theirs. Counsel paperwork, in deadline order: a short board consent memorializing founder alignment (before the repository goes public); the plugin exception text (before the first outside contribution is accepted — see the module boundary above); the trademark policy (before public launch). **Resolved 2026-07-10:** the contribution instrument is the DCO. The CLA that earlier drafts of this charter required is dropped — its consolidated-copyright rationale was wrong, and the relicensing power a CLA holds in reserve is precisely what the self-hosted audience this project serves has learned to distrust. +Helpthread preserves meaningful operator choice over deployment, data, configuration, credentials, integrations, extensions, and AI providers. -This charter is not legal advice. Counsel reviews the licensing structure, the plugin exception, and the trademark policy, each by its deadline above. +Operators choose the infrastructure providers through which they operate Helpthread. Those providers may offer managed infrastructure, but Helpthread has no project-operated data path: conversation data stays within the infrastructure and services the operator selects. -## 4. Architecture direction +The operator-owned deployment path must remain real, documented, and functionally credible. -Serverless kills the traditional runtime plugin-folder model — there's no long-lived filesystem to drop a module into. Helpthread's extension model is designed in from day one along two tracks: **build-time npm modules**, where an operator adds a package and redeploys — the install *is* the Vercel build — and **typed event hooks / webhooks** for integrations that live out-of-process entirely. The plumbing paid modules need (license keys, license-gated distribution, an update channel) is built alongside the modules themselves and exercised through the project's own installs long before a stranger's money touches it — never as a runtime restriction: a license authenticates downloads and updates, and no shipped module ever phones home or stops working when a license lapses (`specs/modules/catalog.md` §5). +### The core stays open -**~~Amended 2026-07-19 (managed hosting)~~ — RESCINDED 2026-07-20 by TJ on review.** TJ selected this option, and the record should say so: asked "Who hosts the module runtime for the v1 in-app install experience?" (2026-07-20T00:12:17Z), he chose **"Managed: RIQ hosts (Recommended)"** at 00:13:15Z. Two things qualify that: the option was labelled *"(Recommended)"* by the assistant that wrote the question, and the alternative offered in the same picker — *"Deploy-button: operator's Vercel"* — is the design he later described as what he had wanted all along. Reviewing the consequences on 2026-07-20 he rejected the model outright: *"i never ever intended that we would be hosting running module code, that makes no sense at all."* A one-click selection under a recommendation is thin consent for narrowing a constitutional promise, and it is reversed here on his instruction. **Resonant IQ hosts no module runtimes.** The operator deploys and runs every module on their own infrastructure, as before; the one-click path is the Vercel deploy-button flow `specs/modules/marketplace-v1.md` §5 already named as additive. No code was ever written against this amendment. Text preserved below, struck, for the record — it states no operative rule. See HT-100. +The Helpthread core is AGPL-3.0 free software. Contributors retain copyright in their work and contribute it under the same license the project gives everyone else. -~~the *default* install path is no longer the operator redeploying a module themselves — clicking Install in Manage → Modules provisions a Resonant IQ-hosted instance of the module for that deployment, the realization of the hosted convenience services §3 already contemplates. The self-host path (download the versioned tarball, deploy to your own Vercel) remains fully supported as the open-core escape hatch. Both preserve the same invariants without exception: a license is only ever a distribution credential — no runtime license check ships in any module, hosted or self-hosted, and the hosted artifact is byte-identical to the self-host tarball; no module phones home; and a lapsed license never stops running software, even when Resonant IQ is the host. The credential-bearing install/update orchestration lives entirely outside the AGPL core, in a Resonant IQ-operated hosting control plane — the one component that holds both a license key and a per-desk provisioning grant; the core holds neither and never calls the marketplace (`specs/modules/marketplace-v1.md` §3/§5).~~ +Capabilities released as part of the free core are not later withdrawn behind a commercial license. Commercial offerings may add modules, updates, support, or other conveniences. They do not subtract from the open core. -**Platform posture: Vercel-first, not Vercel-only.** The first-class deployment target is Vercel + Supabase, and the deploy story is optimized for it without apology. But the engine's core never calls a platform directly: queueing, scheduled and durable work, blob storage, and inbound email all sit behind thin provider interfaces the project owns, with today's implementations (Vercel Queues, Vercel Cron and Workflows, Supabase Storage, Gmail push) as adapters rather than assumptions. Inbound email forces this discipline anyway — Gmail can't be the only supported mailbox forever — and applying it to the other seams keeps a future plain-Node-plus-Postgres deployment mode reachable without an engine rewrite. Supabase itself is open source and self-hostable, so that half of the stack is a soft dependency by construction. No additional deployment targets are promised at launch: every supported target is a permanent test matrix, and that cost gets taken on only when demand justifies it. +Helpthread sustains itself through commercial software and services around the core, not by selling exceptions to the core’s license. -The conversation model is **channel-agnostic from day one**. Email is the founding channel — but a conversation and its threads don't care how a message arrived, and the schema never assumes SMTP. Chat/messaging arrives later as a second channel over the same engine (Supabase Realtime is the push transport; a "chat" is a conversation whose threads travel a faster wire), and an embeddable support widget — knowledge-base search, start a conversation, follow the replies, in the tradition of Help Scout's Beacon — is a planned first-party module built on the same public API. Live-chat trappings like presence and typing indicators layer onto that channel per-operator; they are staffing promises more than plumbing. +### Conversation integrity -The actor model is **AI-ready from day one**. Every thread records what kind of actor authored it — customer, human staff, or AI. AI-authored work supports draft-before-send states, human-approval handoffs, and a full audit trail. Like channels and modules, these are day-one schema shapes, not features: they cost a column now and a migration crisis later. Vocabulary, fixed here to prevent permanent confusion: **agents** are human support staff; **assistants** are AI actors. +Helpthread preserves the fidelity, provenance, and durable history of conversations. -The knowledge base ships as a first-party paid module — **content-as-code** (docs live in git, build to static output, search index generated at build time) as its initial form, a runtime editor later — not as core capability. (Reclassified 2026-07-19, HT-75, reversing the original day-one core commitment; see the licensing section above and `specs/modules/catalog.md`.) +Every message records who or what authored it, how it arrived, and where it belongs. Threading authority derives from evidence Helpthread can verify. Changes to communication behavior require fixtures, reproducible proof, or an explicit written decision—not intuition alone. -The founding public API surface is six conversation operations: list conversations by customer email, get a conversation with its threads, get a conversation's owner, create a conversation (with attachments), add a customer reply, and look up a customer — the customer-side contract, matching what a production integration already consumes. The agent-side API grows in lockstep with the inbox UI under the API-first rule: no UI capability ships without its public API underneath. Everything else — mailbox management, roles, workflows, reporting — gets built on top of and around this surface, not ahead of it. +Email is the founding channel, so its mature semantics and accumulated edge cases deserve particular respect. The rule is broader than email: correctness follows the conversation across every channel. -## 5. Roadmap +The following are never traded for speed, convenience, or a convincing demonstration: -No dates; phases are ordered by dependency, not calendar. +1. Never lose or corrupt a message. +2. Threading correctness outranks feature velocity. +3. Authorship and provenance remain explicit. +4. Communication semantics do not change silently. -- **Phase 0 — Foundations.** Name, domains, GitHub org, and npm org secured (done). This charter. A public-ready engine repo with clean history. -- **Phase 1 — Core engine, dogfooded.** Mail engine: event-driven ingestion (bounded reconciliation and intake fetches, never a long-running poller), parsing, threading, sending, signed reply tokens, auto-responder handling, bounce handling, HTML sanitization. The six-operation conversation API. An agent inbox UI. Gmail push for inbound. This runs as Resonant IQ's actual production support desk before it runs as anyone else's. -- **Phase 2 — Production cutover.** Resonant IQ retires its FreeScout instance and switches to Helpthread at the config level — a clean cutover with no legacy data requiring migration. -- **Phase 3 — Public launch.** Deploy-to-Vercel button, public docs site, and the start of a community — **with the marketplace live on day one**: license keys, module distribution, and the first paid modules (the knowledge base and AI-powered modules — draft-reply suggestions, auto-triage, KB-grounded auto-answers in the widget — lead the catalog), all having served as Resonant IQ's own install path through the dogfood phase. A helpdesk launched free with its commercial layer already real avoids the worse story: a community formed around ambiguity about how the project sustains itself. +### Public APIs and events -The full helpdesk surface — mailbox management, roles and permissions, workflows, SLAs, saved replies, reporting, admin, search, and the chat channel plus embeddable support widget described in the architecture section — gets built incrementally, prioritized by what real usage demands. The map of that territory is drawn from the helpdesks we know best — Help Scout's experience and FreeScout's feature surface — as a guide, not a contract; the measure Helpthread holds itself to is Help Scout's ease of use, for customers and operators alike. The mail engine, for all the care it gets, is only something like 10–15% of that eventual surface. This is a multi-month-plus endeavor, not a weekend rewrite, and nobody involved should pretend otherwise. +Anything a first-party interface can do must also be possible through a documented public contract. -## 6. Sacred invariants +The operator inbox is a client. Automation is a client. AI is a client. Future interfaces are clients. -Five things we do not trade away for speed, convenience, or a good demo: +Meaningful state changes emit meaningful events. Public contracts expose enough context and provenance for operators and extensions to understand what happened without relying on private implementation details. -1. Never lose or corrupt customer mail. -2. Provenance purity — no copyleft-derived code enters the shipping tree. -3. Threading correctness outranks feature velocity. -4. Main stays releasable. -5. No silent scope creep in mail semantics — see the equivalence rule in the principles. +### Replaceable providers -## 7. Governance +External services are dependencies, not identities. -Solo-maintainer, BDFL model for now — there is one project, one person accountable for it, and that's honestly where it is. Contributions are accepted under the DCO — every commit signed off, contributors keep their copyright — per the licensing section above. Conventional-commit conventions and PR review norms will get written down once there are contributors to write them down for; inventing process ahead of contributors is its own kind of scope creep, so this section stays short on purpose. +Helpthread owns the interfaces at its platform boundaries. Providers implement those interfaces. A first-class provider may receive an excellent, deeply tested experience without becoming inseparable from the core. ---- +Replacement does not need to be effortless. It must remain architecturally possible. -**Status:** Written 2026-07-09, prior to the first code commit. This charter is the founding document of the Helpthread project and precedes any implementation. +### Extensibility without privilege -**Amended 2026-07-10 (HT-21):** §3 and §7 — the CLA/consolidated-copyright model replaced with DCO-only contributions; the module boundary's plugin exception promoted to the load-bearing legal mechanism, its counsel deadline moved to before the first external contribution; dual-licensing of the core deliberately given up; trademark policy added to pre-launch counsel work. +Meaningful ownership includes the ability to extend the system. -**Amended 2026-07-19 (HT-79):** §3, §4, §5 — the marketplace moves from "a later phase, once demand justifies it" to a launch-day component of Phase 3, built now and proven as the project's own dogfood install path. Rationale: the original deferral assumed the module substrate and first modules would not exist until after launch; both shipped during the dogfood phase, so the remaining marketplace work is commerce plumbing, not speculation — and launching the free helpdesk with its sustainability model already visible is the more honest story. The §7 plugin exception's counsel deadline is unchanged (before first external contribution): every v1 marketplace module is out-of-process and needs no exception. New counsel items before the marketplace takes real money: the commercial module license text and terms of sale. +First-party, community, and private extensions use the same public mechanisms. If a first-party module needs a new capability, that capability belongs in the public extension model. Commercial code receives no hidden privileges. -**~~Amended 2026-07-19 (HT-79, managed hosting)~~ — RESCINDED 2026-07-20 (HT-100).** Same origin as the §3 amendment above: inferred by an assistant, never decided by TJ, merged in PR #98 twenty-six minutes after opening with zero human review. **Resonant IQ hosts no module runtimes.** Struck text follows for the record. +Extension points exist because the platform needs them, not as private accommodations for particular modules. -~~§4 — the marketplace's default install path becomes **managed hosting**: clicking Install in Manage → Modules provisions a Resonant IQ-hosted instance of the module, the realization of the "hosted convenience services" §3 already names. Decided after TJ walked the manual self-host install as customer #1 (HT-82 dogfood) and rejected its friction. The posture invariants are restated, not relaxed: (a) a license key stays a distribution credential only — no runtime license check exists in any module, hosted or self-hosted, and the hosted artifact is byte-identical to the self-host tarball; (b) a lapsed license never stops running software — a hosted instance keeps running at its entitled version, updates simply stop; (c) the AGPL core holds no license key and no marketplace credential and never calls the marketplace — the credential-bearing install/update orchestration lives entirely outside the core, in a Resonant IQ-operated hosting control plane, the only trust domain holding both license keys and per-desk provisioning grants. The self-host tarball path remains fully supported as the open-core escape hatch, and a refunded/revoked customer's already-held self-host copy keeps running with no DRM and no runtime check, ever — a consciously accepted residual exposure. Managed hosting is opt-in per operator; the core mail/data path is unchanged. Left to `specs/modules/marketplace-v1.md`, not settled by *this* amendment: the exact refund-window and config-export-grace figures, and the scoped per-desk provisioning-credential the substrate must add. The reconciliation of §2's own-your-data wording with a hosted module processing operator data on Resonant IQ infrastructure was flagged here as a data-residency call for TJ and **resolved the same day** by the own-your-data scoping amendment immediately below (HT-5/HT-82); the managed-hosting data-handling terms themselves remain on the §8 pre-revenue counsel gate.~~ +The boundary between the AGPL core and separately licensed modules must be explicit, documented, and equally available to everyone. Repository separation alone does not define that boundary. -**~~Amended 2026-07-19 (HT-5/HT-82, own-your-data scoping)~~ — RESCINDED 2026-07-20 (HT-100).** This amendment existed only to carve out managed hosting from the own-your-data promise. With managed hosting rescinded above, the exception has nothing to except: §2 is restored to its absolute form and carries no carve-out. Leaving this amendment standing would have left the constitution contradicting itself — §2 promising no exception while the appendix asserted one. Struck text follows for the record. +### Infrastructure before applications -~~§2 — the "own your data" bullet's promise is stated as *conversation data never touches Resonant IQ-operated infrastructure — we don't sit in the data path*, with one explicit, opt-in exception: a module an operator enrolls in Resonant IQ's managed hosting processes that operator's conversation data (and holds the operator-supplied keys it needs) on Resonant IQ infrastructure, under disclosed data-handling terms consented to at enrollment. This also corrects a mis-scoped earlier phrasing that put "the core" out of the data path — the operator-run core *is* the data path; the promise that matters is that **Resonant IQ-operated** infrastructure stays out of it. Rationale: the managed-hosting amendment (same date, above) made an unqualified promise falsifiable for enrolled operators; a data promise that is precisely true beats one that is absolutely worded and false in an opt-in case. The mail engine and system of record remain on the operator's own accounts in every configuration, and the self-host path preserves the original absolute promise. Decided by TJ acting as counsel, 2026-07-19; the managed-hosting data-handling terms themselves remain on the §8 pre-revenue counsel gate (`specs/modules/marketplace-v1.md` §8).~~ +The core provides durable primitives. Optional applications compose them into particular experiences. +Knowledge bases, AI assistants, analytics, reporting, and specialized workflows may integrate with or build upon Helpthread. They do not become part of the foundation merely because they are valuable. -**Amended 2026-07-19 (HT-75):** §3 and §4 — the knowledge base reclassified from free-forever core to a paid first-party module. FreeScout's own reference instance runs its Knowledge Base as a paid purchase (`specs/ui/admin-ia.md`); keeping ours core was chosen 2026-07-18 as a marketed free-vs-FreeScout differentiator (`specs/modules/catalog.md` §1), and TJ reversed that call on review. Not a retroactive paywall under the §3 invariant — the knowledge base was never shipped (`specs/modules/catalog.md` §2.1's shipped list never included it). Full paid-module catalog: `specs/modules/catalog.md`. +When a capability can reasonably live outside the foundation, it should. Extensibility is preferred over completeness. -**Amended 2026-07-20 (HT-92, scheduled-fetch intake):** §2 — the "Serverless-native" bullet's prohibition is restated from *"no daemons, no polling loops"* to **"no daemons, no long-running processes."** Inbound mail may arrive either by push webhook or by a **bounded, stateless scheduled fetch** — a function that connects, fetches from a stored cursor, and exits. **Neither transport is designated primary.** The operator picks one at setup and trades latency against setup cost — push is near-instant but requires a GCP project and ~6 provisioning steps plus two known org-policy landmines (`specs/deploy/gmail-inbound-runbook.md` Part A); scheduled fetch costs up to a cron interval and requires an app password or OAuth. Consequential wording updated at §1's opening summary, the FreeScout comparison, the serverless positioning line, and §4's Phase 1 description. +### Interfaces may change; contracts remain stable -*Wording ratified by TJ, 2026-07-20.* He initiated the change — *"Wait, wait, wait - why would we forbit imap polling?"* and *"Wasn't the no polling clause just to say... we as Helpthread will never poll their code?"* — gated it on evidence (*"let's get this fully tested out before we change the charter. I want to see it in action first"*), and approved the §1 positioning line verbatim: *"push-based delivery where providers offer it, bounded scheduled fetches where they don't — and no resident process either way."* An earlier draft of this amendment elevated scheduled fetch to a *"first-class primary transport"*; that framing was the drafting assistant's, not TJ's, and was removed on review — the operator, not this charter, chooses the transport. +No interface may become the only expression of a capability. -Rationale: the original wording conflated two separable things — a *daemon* (a process that stays resident and holds an open connection, which is what makes FreeScout require a persistent PHP host) and a *scheduled fetch* (an invocation that ends). Only the first is incompatible with serverless. §4's Phase 1 language already permitted "bounded reconciliation fetches, never a long-running poller," and the daily watch-maintenance cron has always been exactly that — it enqueues the same reconcile job the push path enqueues, and that job reads the mailbox's *stored cursor*, never the push notification's `historyId`. The engine already did on a schedule what §2 appeared to forbid; this amendment removes an internal inconsistency rather than introducing a capability. +Public schemas, APIs, events, and extension contracts evolve deliberately and compatibly. Internal implementations may change freely while their public meaning remains stable. -What it unlocks: making Pub/Sub **optional rather than mandatory** — six setup steps an operator no longer has to complete, including the two that fail silently (the domain-restricted-sharing org-policy block and the missing `serviceAccountTokenCreator` grant). Gmail push itself remains fully supported and preferred where an operator wants sub-minute latency; only its status as a setup prerequisite changes. It also unlocks IMAP/SMTP mailbox connection via app password, which needs no DNS records, no MX change, no cloud console, and no third-party provider account. Measured against the ~26-step, 4-console setup an adopter faces today (HT-82 dogfood), this is the difference between Helpthread being evaluable and not being evaluated. §1's own standard — *operators should find running the thing dead easy* — was not being met. +### Provenance must be defensible -Verified before amending (2026-07-20, live against the dogfood desk): IMAP + app password authenticated `help@resonantiq.app` and returned raw RFC822 with a real reply's `ht.ht1.…` signed token intact in its `References` chain, connection closed on exit; SMTP + the same credential sent with our own `Message-ID` preserved verbatim through Gmail's submission path. +Helpthread is an independent implementation. -What does not change: no process may stay resident — no IMAP IDLE, no held connections, no worker loops; each fetch is a fresh bounded invocation. Threading authority stays on the outbound Message-ID (§2). Mail semantics remain sacred (§2) — any new transport ships with fixtures proving equivalence before it becomes a default. The data-path promise (§2, "Own your data") is untouched, and is strengthened: a direct mailbox connection puts fewer third parties in the path than a provider webhook would. +Code entering the shipping tree must have a clear and compatible origin. Behavior may be learned from public standards, public documentation, permissively licensed references, and black-box observation—not by copying code under an incompatible license. -Positioning consequence, accepted deliberately: §1 previously sold against FreeScout partly on this line. The honest differentiator survives and is restated — FreeScout requires a host running resident scheduler daemons; Helpthread runs the same logical work as stateless invocations on infrastructure the operator does not administer. The claim was never truthfully "we never fetch on a schedule," since the engine always did. It is "nothing of ours stays running." Decided by TJ, 2026-07-20. +Defensible provenance protects users, contributors, the open-source license, and the commercial ecosystem. + +### Operational simplicity + +Organizations adopt Helpthread to support their customers, not to operate support software. + +The system should be understandable when inspected and unobtrusive when working. Added operational complexity must provide a clear benefit in reliability, capability, or operator choice. + +## Non-Goals + +Helpthread is not: + +- a CRM +- a customer success platform +- a marketing or sales platform +- a business or customer intelligence platform +- a team chat application +- an all-in-one business suite + +These systems may integrate with or build upon Helpthread. They are not Helpthread. + +This boundary describes the product, not the conversations it may carry. Helpthread may support sales, onboarding, success, internal service, or other conversations without becoming the system that manages those business functions. + +Helpthread is also not defined by: + +- a particular industry or business model +- the word “customer” as the only name for a person being served +- a single communication channel +- a single deployment provider +- a single user interface +- a single AI provider or model + +Support is Helpthread’s purpose and category. Its architecture must not confuse its first market, channel, or implementation with the permanent boundary of the system. + +## Architecture + +Architecture records today’s consequences of the charter. It may change as long as the first principles and project commitments remain intact. + +### Founding deployment posture + +Helpthread is serverless-native and optimized first for Vercel and Supabase because that is where it was created and first operated. + +Queueing, scheduled and durable work, object storage, inbound communication, and similar services sit behind Helpthread-owned provider interfaces when those interfaces preserve a meaningful boundary. + +Vercel and Supabase are the founding deployment target, not the definition of Helpthread. Every additional supported target becomes an enduring compatibility and testing commitment. + +### Conversation model + +The conversation model is channel-agnostic. Email is the founding channel, but storage and public contracts do not assume it is the only way a message can arrive. + +A conversation is composed of participants, messages, events, ownership, and history. Channel-specific behavior belongs at the boundary. + +### Actor model + +Authorship and provenance are explicit. Human staff, external participants, automated systems, and AI assistants are never silently conflated. + +AI may draft, summarize, route, or act through public contracts. Its work remains attributable, reviewable when policy requires it, and subject to the same operator choice as any other provider. + +### Application and module model + +Helpthread supports build-time modules and out-of-process integrations through documented public boundaries. Out-of-process composition is preferred when it provides a clean and sufficient contract. + +First-party paid modules may exist. They are distributed to and deployed by the operator; Resonant IQ, Inc., the company that develops and stewards Helpthread, does not host their runtime or process their conversation data. The knowledge base is one such application: the core may expose public knowledge interfaces and integration hooks, while authoring, publishing, presentation, and management remain module capabilities. + +Commercial licensing may govern access to downloads, updates, or support. It must not turn installed software into a remote-controlled runtime. A lapsed license may end access to future value; it does not disable software already in the operator’s possession. + +### Founding product surface + +The free core includes the conversation engine, threading, identity, assignment, durable history, an operator inbox, public APIs and events, and the infrastructure needed to operate them. + +The exact API surface and product roadmap belong in maintained specifications. They are not frozen by this charter. + +## Stewardship and Amendments + +The current governance model belongs in the project’s governance documentation. It may evolve without changing this charter. + +Resonant IQ, Inc. stewards the Helpthread name, official distribution channels, marketplace, and its own contributions. This stewardship does not grant it private capabilities within the open core or ownership of contributors’ work. + +Changes to implementation follow the normal development process. Changes to this charter require: + +1. a written proposal identifying the principle or rule affected; +2. the reason the existing charter no longer produces the right decision; +3. the alternatives considered; +4. an explicit decision recorded in the amendment history. + +An amendment should clarify or deliberately change the project’s identity—not document a feature or ordinary architectural choice. + +The first principles may be amended only when the project deliberately chooses to become something materially different. Project commitments may evolve when experience shows that they no longer protect those principles. Architecture changes through ordinary design work. + +## Success + +Helpthread succeeds when organizations can build and operate the support systems they need without giving up meaningful choice or control. + +> **Technology will change. These principles should not.** + +## Amendment History + +### 2026-07-23 — Founding Charter v2 adopted + +This charter replaced the original founding document after the project clarified its identity as open-source support infrastructure. The original charter and its amendment history remain available at [`docs/history/CHARTER-v1.md`](docs/history/CHARTER-v1.md). + +Future entries record the date, scope, decision, and rationale for each adopted amendment. Superseded implementation decisions move to the [decision log](docs/decisions/README.md) rather than accumulate here. diff --git a/CLAUDE.md b/CLAUDE.md index 410c407..b0051f1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # Helpthread — engine repo -Helpthread is an open-source serverless helpdesk (AGPL-3.0 core; a Resonant IQ, Inc. product). **CHARTER.md is the constitution** — read it before substantive work. +Helpthread is open-source support infrastructure (AGPL-3.0 core; a Resonant IQ, Inc. product). **CHARTER.md is the constitution** — read it before substantive work. ## Delegation ladder @@ -16,8 +16,12 @@ Shared rule (mirrored from the resonantiq canonical doc) — imported so it load - **postal-mime** (MIT-0) — modern serverless MIME parsing; the parsing dependency. - **Chatwoot** (MIT core; the `enterprise/` folder is NOT MIT — exclude it) — behavioral/feature reference, adaptable with attribution. - Modern TS/AI helpdesks (e.g. antiwork/helper) may be *looked at* for UX/AI patterns, but **their code is not adapted unless a permissive LICENSE is confirmed** — as of 2026-07-10 helper's and cossistant's licenses did not resolve on GitHub, so: look-only. -- **FreeScout's role: a window into the Help Scout experience, nothing more.** Help Scout (closed SaaS) is the ease-of-use North Star; FreeScout is the open, self-hostable pane of glass we use to *model the interface* toward that bar. It is a UX/experience reference, never a code source. Its AGPL source is never read in a Helpthread session (the operating habit lives in `CLAUDE.local.md`). -- Behavior is specified from RFCs, public documentation, and — where already captured — the black-box fixtures; we do not observe FreeScout further. +- Product and interface decisions are governed by Helpthread's charter and specifications, + not by competitor parity. Existing black-box observations may provide evidence for a + behavior, but they do not define the product. Copyleft-licensed projects are never code + sources and their source is not read in a Helpthread development session. +- Behavior is specified from RFCs, public documentation, and — where already captured — + black-box fixtures. - Every substantive change gets real human review before merge — ordinary PR review, preserved in git history. This is also what keeps AI-assisted work copyrightable; a rubber stamp doesn't meet that bar. ## Coding discipline @@ -31,10 +35,10 @@ Adapted in our own words from Andrej Karpathy's observations on LLM coding pitfa ## Workflow -- Branches: `/ht--` (Jira project **HT**). PRs to `main`; `main` stays releasable (charter invariant #4). No direct pushes to `main` after Phase 0. +- Branches: `/`. PRs to `main`; `main` stays releasable. No direct pushes to `main` after Phase 0. - Commit author email stays the noreply address already set in `.git/config` (GitHub email-privacy blocks the real one). - Delegate work to subagents on the cheapest capable model: Haiku for mechanical, Sonnet for standard implementation, top-tier only for correctness-critical reasoning. -- Mail semantics are sacred (charter §2, invariant #5): changes require fixture-proven equivalence or explicit written justification. Verify against reality before claiming done; put the evidence in the PR. +- Mail semantics are sacred under the charter's "Conversation integrity" rule: changes require fixture-proven equivalence or explicit written justification. Verify against reality before claiming done; put the evidence in the PR. ## Vocabulary @@ -44,7 +48,7 @@ Adapted in our own words from Andrej Karpathy's observations on LLM coding pitfa ## UI fidelity (TJ, 2026-07-12) -The Agent Inbox UI's pixel source of truth is the Claude Design prototype — `Helpthread App.dc.html` in the "Helpthread Agent Inbox Design" project (the "Helpthread" design-system project carries the same components). **The dogfood site must match it exactly — the whole designed surface, not a subset.** Any deviation — visual, copy, or interaction — requires TJ's explicit sign-off. Remaining gaps are tracked as the fidelity checklist on [HT-23](https://resonantiq.atlassian.net/browse/HT-23); the ticket is not done until the checklist is. +The Agent Inbox UI's pixel source of truth is the Claude Design prototype — `Helpthread App.dc.html` in the "Helpthread Agent Inbox Design" project (the "Helpthread" design-system project carries the same components). **The dogfood site must match it exactly — the whole designed surface, not a subset.** Any deviation — visual, copy, or interaction — requires TJ's explicit sign-off. The work is not complete until the maintained fidelity checklist is clear. **Design and app reconcile in both directions (TJ, 2026-07-20).** The two are one system, and neither is allowed to silently drift from the other: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 43f51fc..d2357b8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Thanks for your interest this early. **Helpthread is not accepting external code ## Why not yet -Helpthread's core is AGPL-3.0, and contributions are accepted under the [Developer Certificate of Origin](https://developercertificate.org/) — you sign off your commits (`git commit -s`), you keep your copyright, and there is no CLA and never will be. Because contributors keep their copyright, the plugin exception that draws the line between the AGPL core and commercial modules (see [CHARTER.md](CHARTER.md) §3) effectively cannot be broadened once outside code lands. **That exception text isn't counsel-final yet.** Until it is, we can't responsibly merge external code — not because your work wouldn't be welcome, but because merging it would freeze legal text we're still getting right, and we'd rather tell you up front than leave a PR hanging. +Helpthread's core is AGPL-3.0, and contributions are accepted under the [Developer Certificate of Origin](https://developercertificate.org/) — you sign off your commits (`git commit -s`), you keep your copyright, and there is no CLA and never will be. Because contributors keep their copyright, the [module API exception](legal/module-api-exception.md) that draws the line between the AGPL core and commercial modules effectively cannot be broadened once outside code lands. **That exception text isn't counsel-final yet.** Until it is, we can't responsibly merge external code — not because your work wouldn't be welcome, but because merging it would freeze legal text we're still getting right, and we'd rather tell you up front than leave a PR hanging. ## What you can do now diff --git a/GOVERNANCE.md b/GOVERNANCE.md new file mode 100644 index 0000000..ce71802 --- /dev/null +++ b/GOVERNANCE.md @@ -0,0 +1,41 @@ +# Helpthread Governance + +This document describes how the Helpthread project is governed today. It may evolve +without changing the project’s [Founding Charter](CHARTER.md). + +## Maintainer model + +Helpthread currently uses a lead-maintainer governance model. One maintainer is +accountable for the coherence of the project and has final authority over product, +architecture, release, and contribution decisions. + +That authority is stewardship. It does not transfer ownership of contributors’ work +or create private capabilities within the open core. + +## Project stewardship + +Resonant IQ, Inc. stewards: + +- the Helpthread name and marks; +- the official repository and distribution channels; +- the official marketplace; and +- its own contributions. + +Contributors retain copyright in their work. Contribution and licensing requirements +are documented in [CONTRIBUTING.md](CONTRIBUTING.md) and +[legal/README.md](legal/README.md). + +## Ordinary decisions + +Implementation and product decisions follow the normal issue and pull-request process. +Current workflow requirements belong in [CONTRIBUTING.md](CONTRIBUTING.md), not the +charter. + +## Charter amendments + +The amendment process is defined by the charter. A proposed amendment must identify +the affected principle, explain why the current charter no longer produces the right +decision, record alternatives, and state the final decision explicitly. + +Historical constitutional decisions are indexed in +[docs/decisions/README.md](docs/decisions/README.md). diff --git a/README.md b/README.md index da6b62f..b078b4e 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,35 @@ # Helpthread -Open-source, serverless helpdesk — shared inbox, threaded email conversations, knowledge base — for teams who live on Vercel and Supabase. Aimed at the ease of use Help Scout set the bar for, fully owned by the operator. FreeScout proved the self-hosted market is real; Helpthread is built for it on modern rails. +**Open-source support infrastructure.** -> **Status: early and pre-release.** This repository is being built in the open from its first day. Today it holds the project's constitution ([CHARTER.md](CHARTER.md)), behavioral specifications, a test-fixture harness, and the platform interfaces — **not yet a runnable product.** It is dogfooded first, as [Resonant IQ](https://resonantiq.app)'s own support system, before it's anything anyone else should deploy. Watch or star to follow along; expect things to move and change. +Helpthread provides the foundation upon which support systems are built: durable +conversations, trustworthy email threading, an operator inbox, public APIs and events, +and an extension model available equally to first-party and community software. -## What's here now +It is serverless-native and optimized first for Vercel and Supabase, while keeping +platform services behind Helpthread-owned interfaces. Organizations deploy Helpthread +into infrastructure they choose and control. -- **[CHARTER.md](CHARTER.md)** — the founding document: vision, principles, licensing, architecture, roadmap. Read this first. -- **`specs/`** — behavioral specifications (the conversation API contract, mail threading) that the engine will be built and tested against. -- **`fixtures/`** — a black-box test harness and recorded fixtures that form the mail engine's acceptance suite. -- **`src/providers/`** — the platform-provider interfaces (queue, scheduler, storage, inbound email) the engine depends on, keeping it Vercel-first but not Vercel-only. +> **Status: pre-release and dogfooded in production.** Helpthread is developed by +> Resonant IQ, Inc. and runs the company's support operation today, but it is not ready for general deployment. See +> [STATUS.md](STATUS.md) for the current product surface and roadmap. + +## Start here + +- **[Founding Charter](CHARTER.md)** — identity, principles, and project commitments +- **[Architecture](docs/architecture/README.md)** — the map to current technical contracts +- **[Status](STATUS.md)** — what is live, underway, and deferred +- **[Governance](GOVERNANCE.md)** — how project decisions are made today +- **[Legal and licensing](legal/README.md)** — the AGPL core and commercial-module boundary +- **[`specs/`](specs/)** — the maintained behavioral and product contracts +- **[`fixtures/`](fixtures/)** — the mail engine's black-box acceptance suite ## Architecture in one breath -TypeScript on Vercel (Fluid Compute, Workflows, Queues, Cron — no daemons, nothing stays resident) plus Supabase (Postgres, Auth, Storage, Realtime). Inbound mail arrives by push webhook where the provider offers one, or by a bounded scheduled fetch where it doesn't — either way as an invocation that ends, never a process sitting in a loop. Threading authority lives on the outbound side: signed reply tokens in the Message-IDs the engine emits. See the charter for the reasoning. +TypeScript on Vercel plus Supabase for Postgres, authentication, storage, and realtime. +Inbound mail arrives through push delivery and bounded reconciliation rather than a +long-running poller. Signed reply tokens minted into outbound messages give Helpthread +verifiable threading authority. See the [architecture overview](docs/architecture/README.md). ## Development @@ -33,8 +49,11 @@ Scripts: ## Contributing -Not accepting external contributions yet — see [CONTRIBUTING.md](CONTRIBUTING.md). Issues and discussion are welcome. +Helpthread is not accepting external code contributions yet. Issues and discussion are +welcome; see [CONTRIBUTING.md](CONTRIBUTING.md). ## License -[AGPL-3.0](LICENSE) for the core. © Resonant IQ, Inc. Commercially-licensed first-party modules will come later via a marketplace; the core stays free software, forever. See the charter's licensing section. +[AGPL-3.0](LICENSE) for the core. © Resonant IQ, Inc. Commercial modules add to the +core; they do not subtract from it. See +[legal and licensing](legal/README.md). diff --git a/STATUS.md b/STATUS.md index 1fb9de4..045616d 100644 --- a/STATUS.md +++ b/STATUS.md @@ -22,18 +22,23 @@ webhooks + assistant actors) that lets AI and third-party modules extend Helpthr touching core — is merged across waves 1–3 and documented for operators (`docs/modules/`); the **first module**, a draft-reply assistant, is scaffolded in its own closed repo. -The charter was amended 2026-07-19 ([HT-79](https://resonantiq.atlassian.net/browse/HT-79)) to +The [Founding Charter](CHARTER.md) was replaced on 2026-07-23 with a focused +constitution defining Helpthread as **open-source support infrastructure**. The original +charter is preserved in [docs/history/](docs/history/), and its implementation, legal, +roadmap, and governance decisions now live in their maintained documents. + +The charter was amended on 2026-07-19 to move the **marketplace from a deferred phase to a launch-day component of Phase 3** — built now, proven first as Resonant IQ's own dogfood install path. That reframes what's next: commerce plumbing (license keys, distribution, update feed) is current work, not speculation. In flight: the marketplace v1 spec is **draft for TJ review** (`specs/modules/marketplace-v1.md`), the passkey login spec is at **draft.3** (`specs/auth/passkeys.md`), and the draft-reply assistant -is being built out against the live substrate ([HT-73](https://resonantiq.atlassian.net/browse/HT-73)). +is being built out against the live substrate. ## Done **Foundation** -- Founding charter ([CHARTER.md](CHARTER.md)) — mission, principles, licensing, architecture, roadmap; amended ([HT-21](https://resonantiq.atlassian.net/browse/HT-21)) to replace the CLA with DCO-only contributions — contributors keep their copyright, and the AGPL-3.0 §7 plugin exception is the sole legal mechanism separating commercial modules. +- [Founding Charter](CHARTER.md) — Helpthread's identity as open-source support infrastructure, its first principles, and its project commitments. The [original charter](docs/history/CHARTER-v1.md) and its complete amendment history remain archived; material decisions now live in [docs/decisions/](docs/decisions/). - Behavioral specs: mail threading (`specs/mail/threading.md`), outbound sending (`specs/mail/sending.md`), conversation store (`specs/store/conversations.md`), the native Agent Inbox API (`specs/api/agent-inbox-v1.md`), and the module substrate (`specs/modules/`). - Platform provider interfaces (`src/providers/`) — queue, scheduler, blob storage, inbound email, and outbound email sender — Vercel-first, not Vercel-only. - Black-box acceptance fixtures (`fixtures/mail/`). @@ -46,58 +51,58 @@ is being built out against the live substrate ([HT-73](https://resonantiq.atlass - **Threading decision** (`src/mail/thread.ts`) — the 5-rule algorithm: a verified token routes a reply to its conversation; no valid token starts a new one; subject is never used. - **Conversation/thread store** (`src/store/`, `src/db/`) — persistence on a thin, portable raw-SQL layer over PGlite (in-process Postgres) locally, the same SQL running on Supabase in production. Keyset-paginated listing; a valid token to a closed conversation reopens it, to a deleted one the caller starts fresh. - **Outbound send** (`src/mail/send.ts`) — mints the reply token into the outbound Message-ID, persists the outbound thread as an outbox item (`pending`→`sent`/`failed`), and hands it to an `EmailSender`. Returns typed outcomes (a delivered message is never reported as failed). -- **Send idempotency + delivery worker** (`src/mail/send.ts`, `src/mail/delivery-worker.ts`, [HT-16](https://resonantiq.atlassian.net/browse/HT-16)) — a required `Idempotency-Key`, an envelope snapshot, and a delivery lease guard `sendReply` against double-sends; a delivery-worker sweep retries failed/pending outbound on the same lease. Delivery is at-least-once, with provider Message-ID dedup as the recommended backstop. -- **Inbound reliability wave** ([HT-45](https://resonantiq.atlassian.net/browse/HT-45)–[HT-48](https://resonantiq.atlassian.net/browse/HT-48), PRs [#46](https://github.com/Helpthread/helpthread/pull/46)–[#49](https://github.com/Helpthread/helpthread/pull/49)) — lease-reclaim for stuck `received` inbound deliveries, inbound attachment bytes persisted to the `BlobStore`, a Gmail OAuth disconnect admin action, and retry-instead-of-ack on lease-held reconcile with token-scoped lease release. +- **Send idempotency + delivery worker** (`src/mail/send.ts`, `src/mail/delivery-worker.ts`) — a required `Idempotency-Key`, an envelope snapshot, and a delivery lease guard `sendReply` against double-sends; a delivery-worker sweep retries failed/pending outbound on the same lease. Delivery is at-least-once, with provider Message-ID dedup as the recommended backstop. +- **Inbound reliability wave** (PRs [#46](https://github.com/Helpthread/helpthread/pull/46)–[#49](https://github.com/Helpthread/helpthread/pull/49)) — lease-reclaim for stuck `received` inbound deliveries, inbound attachment bytes persisted to the `BlobStore`, a Gmail OAuth disconnect admin action, and retry-instead-of-ack on lease-held reconcile with token-scoped lease release. -**Agent Inbox API v1 + v1.1 — the designed contract, implemented** (`src/api/`; spec amended in [HT-25](https://resonantiq.atlassian.net/browse/HT-25), the surface the UI prototype was built against) +**Agent Inbox API v1 + v1.1 — the designed contract, implemented** (`src/api/`; the surface the UI prototype was built against) - Native, framework-agnostic `Request → Response`; constant-time Bearer auth that runs *before* routing; native `{ error: { code, message } }` envelope; `Cache-Control: no-store`; UUID-shape guards; a top-level catch so nothing leaks as an uncontrolled 500. - Core routes: list (`GET /api/v1/conversations`, folder filter + keyset cursor) · detail (`GET …/{id}`) · reply (`POST …/{id}/replies`) · status (`PATCH …/{id}`). -- **Four-state status model** ([HT-26](https://resonantiq.atlassian.net/browse/HT-26)) — `active/pending/closed/spam`; the list filter is a FOLDER (`open` = active + pending); replies reopen closed and spam, pending is an Agent statement never set or cleared automatically. -- **`number` + `preview`** ([HT-27](https://resonantiq.atlassian.net/browse/HT-27)), **internal notes** ([HT-28](https://resonantiq.atlassian.net/browse/HT-28)), **tags** ([HT-29](https://resonantiq.atlassian.net/browse/HT-29)), **soft delete** ([HT-30](https://resonantiq.atlassian.net/browse/HT-30)), **single-Agent assignee** ([HT-31](https://resonantiq.atlassian.net/browse/HT-31)), and **open tracking, default OFF** ([HT-32](https://resonantiq.atlassian.net/browse/HT-32) — absent config means byte-identical mail and nothing recorded). +- **Four-state status model** — `active/pending/closed/spam`; the list filter is a FOLDER (`open` = active + pending); replies reopen closed and spam, pending is an Agent statement never set or cleared automatically. +- **`number` + `preview`**, **internal notes**, **tags**, **soft delete**, **single-Agent assignee**, and **open tracking, default OFF** — absent config means byte-identical mail and nothing recorded. **Provider adapters** -- **Gmail `EmailSender`** (`src/providers/adapters/gmail/`, [HT-19](https://resonantiq.atlassian.net/browse/HT-19)) — builds a raw RFC 5322 MIME message (mimetext, hardened against header injection, over-long lines, and `References` folding), base64url-encodes it, and sends via `users.messages.send`. A wire-level test proves our Message-ID is transmitted verbatim. -- **Postgres `Db`** (`src/db/postgres.ts`, [HT-20](https://resonantiq.atlassian.net/browse/HT-20)) — a pooler-safe implementation of the same `Db`/`Queryable` seam as PGlite, wrapping `pg`, with per-transaction `search_path` scoping (survives Supabase's transaction-mode pooler). The store and migrations run unchanged against it. +- **Gmail `EmailSender`** (`src/providers/adapters/gmail/`) — builds a raw RFC 5322 MIME message (mimetext, hardened against header injection, over-long lines, and `References` folding), base64url-encodes it, and sends via `users.messages.send`. A wire-level test proves our Message-ID is transmitted verbatim. +- **Postgres `Db`** (`src/db/postgres.ts`) — a pooler-safe implementation of the same `Db`/`Queryable` seam as PGlite, wrapping `pg`, with per-transaction `search_path` scoping (survives Supabase's transaction-mode pooler). The store and migrations run unchanged against it. -**Agent Inbox UI — live** ([HT-23](https://resonantiq.atlassian.net/browse/HT-23), PRs [#64](https://github.com/Helpthread/helpthread/pull/64)–[#66](https://github.com/Helpthread/helpthread/pull/66)) -- The Claude Design hand-back (design system + prototype) built out as the real Next.js frontend over the v1.1 API, deployed standalone (project rooted at `web/`) to **inbox.resonantiq.app**: conversation list + detail skeleton, optimistic status transitions, attachment rendering. Design-system files under `web/src/components/ds/` are verbatim copies of the Claude Design source; deviations require sign-off (fidelity checklist tracked on HT-23). +**Agent Inbox UI — live** (PRs [#64](https://github.com/Helpthread/helpthread/pull/64)–[#66](https://github.com/Helpthread/helpthread/pull/66)) +- The Claude Design hand-back (design system + prototype) built out as the real Next.js frontend over the v1.1 API, deployed standalone (project rooted at `web/`) to **inbox.resonantiq.app**: conversation list + detail skeleton, optimistic status transitions, attachment rendering. Design-system files under `web/src/components/ds/` are verbatim copies of the Claude Design source; deviations require sign-off. -**Live deployment** ([HT-43](https://resonantiq.atlassian.net/browse/HT-43)) +**Live deployment** - Engine on Vercel (project `helpthread`) at **desk.resonantiq.app**; `/api/**` routed to `createInboxApi`; UI standalone at **inbox.resonantiq.app**; Supabase Postgres; GCP project `helpthread-desk` driving Gmail push for `help@resonantiq.app`. As-deployed values and console gotchas are recorded in `specs/deploy/gmail-inbound-runbook.md`. -- Live-only Gmail bugs found and fixed against the running instance: reply token now carried in `References` with self-echo suppression ([HT-49](https://resonantiq.atlassian.net/browse/HT-49)), and self-echoed Gmail reconcile sends/drafts skipped ([HT-50](https://resonantiq.atlassian.net/browse/HT-50)). +- Live-only Gmail bugs found and fixed against the running instance: reply token now carried in `References` with self-echo suppression, and self-echoed Gmail reconcile sends/drafts skipped. -**Per-Agent identity, login & auth** ([HT-54](https://resonantiq.atlassian.net/browse/HT-54); operator login groundwork in [HT-51](https://resonantiq.atlassian.net/browse/HT-51)) +**Per-Agent identity, login & auth** - Engine layer (PRs [#70](https://github.com/Helpthread/helpthread/pull/70), [#72](https://github.com/Helpthread/helpthread/pull/72), migration 18) — per-Agent identity, login, user management, plus mailbox-access grants + an admin API and an admin-IA fidelity doc. - Web layer (PR [#71](https://github.com/Helpthread/helpthread/pull/71)) — per-Agent login, session identity, and team management, retiring the single shared operator password. -**Inbound observability** ([HT-44](https://resonantiq.atlassian.net/browse/HT-44), PR [#73](https://github.com/Helpthread/helpthread/pull/73), migration 19) +**Inbound observability** (PR [#73](https://github.com/Helpthread/helpthread/pull/73), migration 19) - A `/internal/health` endpoint (the alertable surface), a forged-token signal on inbound deliveries, and closed logging gaps. Deployed and live-verified. **Module substrate — the open-core extension foundation** (`specs/modules/`) -- **Module catalog & the open-core line** ([HT-66](https://resonantiq.atlassian.net/browse/HT-66), PR [#75](https://github.com/Helpthread/helpthread/pull/75), `specs/modules/catalog.md`) — the canonical free-vs-paid decision: free core = parity + hygiene (passkey login is core), paid = intelligence, channels, enterprise (SSO is the auth paywall). Born-proprietary discipline; one-way asymmetric line; **Modules**, never "plugins" (the word survives only in the legal *plugin exception*). -- **Substrate v1 spec** ([HT-67](https://resonantiq.atlassian.net/browse/HT-67), PR [#76](https://github.com/Helpthread/helpthread/pull/76), `specs/modules/substrate-v1.md`) — three surfaces, all core-AGPL and free forever: typed event emission, signed webhook delivery, and assistant (AI) actors that authenticate and post draft-before-send work. Additive-forward rule (marketplace attaches, never retrofits); licensing stays distribution-side, never runtime. -- **Wave 1** ([HT-68](https://resonantiq.atlassian.net/browse/HT-68), PR [#77](https://github.com/Helpthread/helpthread/pull/77)) — the actor model and its schema floor: migrations **020–023** land the `assistants` principal table, the `threads` actor model + draft lifecycle, `webhook_endpoints`, and the `event_outbox`. -- **Wave 2** ([HT-69](https://resonantiq.atlassian.net/browse/HT-69), PR [#79](https://github.com/Helpthread/helpthread/pull/79)) — typed events, the outbox drain, signed webhook delivery, and the module admin API. -- **Wave 3** ([HT-70](https://resonantiq.atlassian.net/browse/HT-70), PR [#80](https://github.com/Helpthread/helpthread/pull/80)) — assistant authentication, the drafts API, and the draft-approval orchestration an Agent drives in core. -- **First module scaffold** ([HT-73](https://resonantiq.atlassian.net/browse/HT-73)) — the draft-reply assistant, born proprietary in its own closed repo (`Helpthread/module-draft-assistant`), bootstrapped as a product-shaped scaffold that will ride the public substrate (events → webhook, API read, assistant draft) and touch core only through those extension points. -- **Operator guide** ([HT-71](https://resonantiq.atlassian.net/browse/HT-71), PRs [#81](https://github.com/Helpthread/helpthread/pull/81), [#84](https://github.com/Helpthread/helpthread/pull/84), `docs/modules/`) — README, assistants-and-drafts, and webhooks documentation for running the substrate, plus a precision follow-up pinning event-transaction scope and payload sensitivity. +- **Module catalog & the open-core line** (PR [#75](https://github.com/Helpthread/helpthread/pull/75), `specs/modules/catalog.md`) — the canonical free-vs-paid decision: the free core supports a capable, secure support operation (including passkey login); paid modules add AI and automation, channels, enterprise capabilities, and self-service surfaces. Born-proprietary discipline; one-way asymmetric line; **Modules**, never "plugins" (the word survives only in the legal *plugin exception*). +- **Substrate v1 spec** (PR [#76](https://github.com/Helpthread/helpthread/pull/76), `specs/modules/substrate-v1.md`) — three surfaces, all core-AGPL and free forever: typed event emission, signed webhook delivery, and assistant (AI) actors that authenticate and post draft-before-send work. Additive-forward rule (marketplace attaches, never retrofits); licensing stays distribution-side, never runtime. +- **Wave 1** (PR [#77](https://github.com/Helpthread/helpthread/pull/77)) — the actor model and its schema floor: migrations **020–023** land the `assistants` principal table, the `threads` actor model + draft lifecycle, `webhook_endpoints`, and the `event_outbox`. +- **Wave 2** (PR [#79](https://github.com/Helpthread/helpthread/pull/79)) — typed events, the outbox drain, signed webhook delivery, and the module admin API. +- **Wave 3** (PR [#80](https://github.com/Helpthread/helpthread/pull/80)) — assistant authentication, the drafts API, and the draft-approval orchestration an Agent drives in core. +- **First module scaffold** — the draft-reply assistant, born proprietary in its own closed repo (`Helpthread/module-draft-assistant`), bootstrapped as a product-shaped scaffold that will ride the public substrate (events → webhook, API read, assistant draft) and touch core only through those extension points. +- **Operator guide** (PRs [#81](https://github.com/Helpthread/helpthread/pull/81), [#84](https://github.com/Helpthread/helpthread/pull/84), `docs/modules/`) — README, assistants-and-drafts, and webhooks documentation for running the substrate, plus a precision follow-up pinning event-transaction scope and payload sensitivity. **Catalog & licensing refinements** -- **KB and portal reclassified to paid** ([HT-75](https://resonantiq.atlassian.net/browse/HT-75), PR [#82](https://github.com/Helpthread/helpthread/pull/82)) — the full 71-module FreeScout listing re-audited item-by-item, closing the gap audit. The knowledge base ships entirely as a paid module (not core), as does the end-user portal; the open-core line is restated as **free = parity and hygiene; paid = intelligence, channels, enterprise, and self-service surfaces**. Passkey login (WebAuthn) stays core, deliberately. -- **Passkey classification reconciled** ([HT-76](https://resonantiq.atlassian.net/browse/HT-76), PR [#85](https://github.com/Helpthread/helpthread/pull/85)) — `specs/auth/agents-and-auth.md` squared with the catalog: passkeys core, enterprise SSO paid. -- **Marketplace becomes launch-day** ([HT-79](https://resonantiq.atlassian.net/browse/HT-79), PR [#86](https://github.com/Helpthread/helpthread/pull/86)) — CHARTER §3/§4/§5 amended: the marketplace moves from "a later phase, once demand justifies it" to a **Phase 3 launch-day component**, because the substrate and first modules shipped during dogfood, leaving commerce plumbing rather than speculation. New counsel items before it takes real money: commercial module license text and terms of sale. The §7 plugin-exception deadline is unchanged (every v1 marketplace module is out-of-process and needs no exception). +- **KB and portal reclassified to paid** (PR [#82](https://github.com/Helpthread/helpthread/pull/82)) — the public module inventory was re-audited item-by-item, closing the gap audit. The knowledge base ships entirely as a paid module (not core), as does the end-user portal; the open-core line is restated as **free = a capable, secure support operation; paid = AI and automation, channels, enterprise, and self-service surfaces**. Passkey login (WebAuthn) stays core, deliberately. +- **Passkey classification reconciled** (PR [#85](https://github.com/Helpthread/helpthread/pull/85)) — `specs/auth/agents-and-auth.md` squared with the catalog: passkeys core, enterprise SSO paid. +- **Marketplace becomes launch-day** (PR [#86](https://github.com/Helpthread/helpthread/pull/86)) — CHARTER §3/§4/§5 amended: the marketplace moves from "a later phase, once demand justifies it" to a **Phase 3 launch-day component**, because the substrate and first modules shipped during dogfood, leaving commerce plumbing rather than speculation. New counsel items before it takes real money: commercial module license text and terms of sale. The §7 plugin-exception deadline is unchanged (every v1 marketplace module is out-of-process and needs no exception). -**Inbox basics** ([HT-76](https://resonantiq.atlassian.net/browse/HT-76)/[HT-77](https://resonantiq.atlassian.net/browse/HT-77)/[HT-78](https://resonantiq.atlassian.net/browse/HT-78), PR [#90](https://github.com/Helpthread/helpthread/pull/90), migrations 24–25) +**Inbox basics** (PR [#90](https://github.com/Helpthread/helpthread/pull/90), migrations 24–25) - **Saved replies & macros** (`src/api/saved-replies.ts`, `src/store/saved-replies.ts`, migration 24 `saved_replies`) — the canned-response surface behind the native API. - **Snooze** (`src/mail/snooze-wake.ts`, migration 25 `conversation_snooze`) — a conversation sleeps until its wake time, then returns to the working folder. - **Send & close** (`src/mail/send.ts`) — reply and resolve in one action, the highest-frequency Agent gesture. ## Next -- **Marketplace v1** ([HT-79](https://resonantiq.atlassian.net/browse/HT-79), `specs/modules/marketplace-v1.md` — draft for TJ review) — the commerce plumbing around the substrate: license keys, subscriptions, module distribution, an update feed. Child tickets scoped by the spec: an in-product module directory ([HT-81](https://resonantiq.atlassian.net/browse/HT-81)) and dogfooding Resonant IQ's own install through the marketplace ([HT-82](https://resonantiq.atlassian.net/browse/HT-82)). -- **Passkey (WebAuthn) login** ([HT-75](https://resonantiq.atlassian.net/browse/HT-75), `specs/auth/passkeys.md` — draft.3, spec only) — the second auth provider on the HT-54 seam, and the first real exercise of that seam's marketplace-boundary claim. No migrations or implementation yet. -- **Build out the draft-reply assistant** ([HT-73](https://resonantiq.atlassian.net/browse/HT-73)) against the live substrate — the first real consumer, proving the module boundary end-to-end. -- **HT-5 counsel work** — plugin exception text (now drafted against the real, shipped substrate API), board consent memo, and trademark policy (charter §3/§7, per the DCO amendment) — gates opening the project to external contributions. Per the HT-79 amendment, the marketplace adds two more before it takes money: commercial module license text and terms of sale. +- **Marketplace v1** (`specs/modules/marketplace-v1.md` — draft for TJ review) — the commerce plumbing around the substrate: license keys, subscriptions, module distribution, an update feed. The spec includes an in-product module directory and dogfooding Resonant IQ's own install through the marketplace. +- **Passkey (WebAuthn) login** (`specs/auth/passkeys.md` — draft.3, spec only) — the second auth provider on the core authentication seam and the first real exercise of that seam's marketplace boundary. No migrations or implementation yet. +- **Build out the draft-reply assistant** against the live substrate — the first real consumer, proving the module boundary end-to-end. +- **Counsel work** — plugin exception text (now drafted against the real, shipped substrate API), board consent memo, and trademark policy (see [legal/README.md](legal/README.md)) — gates opening the project to external contributions. Before the marketplace takes money, the commercial module license and terms of sale must also be final. ## Not yet / deferred @@ -106,4 +111,4 @@ is being built out against the live substrate ([HT-73](https://resonantiq.atlass --- -_Last updated: 2026-07-19_ +_Last updated: 2026-07-23_ diff --git a/docs/architecture/README.md b/docs/architecture/README.md new file mode 100644 index 0000000..f53bccc --- /dev/null +++ b/docs/architecture/README.md @@ -0,0 +1,64 @@ +# Helpthread Architecture + +This document is the entry point to Helpthread’s current architecture. The +[Founding Charter](../../CHARTER.md) defines the enduring constraints; specifications +define today’s implementation. + +## Platform posture + +Helpthread is serverless-native and optimized first for Vercel and Supabase. Those +services are the founding deployment target, not the project’s identity. + +Platform services sit behind Helpthread-owned interfaces when the boundary is +meaningful: + +- queueing; +- scheduling and durable work; +- object storage; +- inbound communication; and +- outbound email. + +See [the provider interface guide](../../src/providers/README.md). + +## Communication model + +The conversation model is channel-agnostic. Email is the founding channel. +Channel-specific behavior stays at the boundary while conversations retain durable +participants, messages, events, ownership, provenance, and history. + +The mail engine’s detailed contracts live in: + +- [inbound ingestion](../../specs/mail/inbound-ingestion.md); +- [threading](../../specs/mail/threading.md); +- [outbound sending](../../specs/mail/sending.md); and +- [Gmail push transport](../../specs/mail/gmail-push.md). + +## API and application model + +The operator inbox, automation, AI, and future interfaces are clients of public +contracts. The maintained Agent Inbox contract lives in +[specs/api/agent-inbox-v1.md](../../specs/api/agent-inbox-v1.md). + +The exact product surface and delivery state live in [STATUS.md](../../STATUS.md). + +## Modules + +Extensions use documented public boundaries. Out-of-process composition is preferred +when it provides a sufficient contract; build-time modules remain available for +capabilities that require deeper composition. + +The canonical module documents are: + +- [module substrate](../../specs/modules/substrate-v1.md); +- [open-core catalog](../../specs/modules/catalog.md); +- [marketplace](../../specs/modules/marketplace-v1.md); and +- [operator guides](../modules/README.md). + +The legal boundary between the AGPL core and separately licensed modules is documented +in [legal/README.md](../../legal/README.md). + +## Architecture decisions + +Material decisions and their history are indexed in +[docs/decisions/README.md](../decisions/README.md). Superseded implementation details +belong there or in specification changelogs rather than in the charter. diff --git a/docs/decisions/README.md b/docs/decisions/README.md new file mode 100644 index 0000000..299a942 --- /dev/null +++ b/docs/decisions/README.md @@ -0,0 +1,72 @@ +# Helpthread Decision Log + +This directory preserves material decisions that explain how the current architecture, +licensing model, and product boundary came to be. + +The [Founding Charter](../../CHARTER.md) contains enduring principles. Decisions and +specification changelogs contain dated implementation and product choices. + +## 2026-07-10 — DCO contributions and the module boundary + +**Decided:** 2026-07-10 + +Contributors retain copyright and contribute under the Developer Certificate of Origin. +Helpthread does not require a CLA or copyright assignment. + +Where separately licensed modules form a combined program with the AGPL core, an +AGPL-3.0 §7 additional permission defines the legal boundary. It is symmetric for +first-party, community, private, and fork-based modules using the documented public API. +Counsel must finalize it before the first external contribution is accepted. + +Helpthread therefore gives up unilateral relicensing and core dual-licensing. Commercial +sustainability comes from additive modules and services rather than license exceptions. + +See [legal/README.md](../../legal/README.md). + +## 2026-07-19 — Knowledge base reclassified as a paid module + +**Decided:** 2026-07-19 + +The knowledge base is a first-party paid module. The core may expose public knowledge +interfaces and integration hooks; authoring, publishing, presentation, search +experiences, and management remain module capabilities. + +This was not a retroactive paywall because no knowledge-base capability had shipped in +the free core. The maintained product boundary lives in +[specs/modules/catalog.md](../../specs/modules/catalog.md). + +## 2026-07-19 — Marketplace moved to public launch + +**Decided:** 2026-07-19 + +The original roadmap deferred the marketplace. Once the module substrate and first +commercial module became real during dogfooding, the remaining work was commerce and +distribution plumbing rather than speculation. + +The official marketplace therefore became a public-launch capability, proven first +through Resonant IQ's own installation path. It remains additive: the AGPL core holds no +marketplace credentials, licensing does not control runtime execution, and commercial +modules receive no private core capabilities. + +See [specs/modules/marketplace-v1.md](../../specs/modules/marketplace-v1.md). + +## 2026-07-23 — Managed-hosting proposal rejected + +A managed-hosting design introduced on 2026-07-19 was based on a mistaken interpretation +of the project direction. Helpthread and its commercial modules are operator-deployed. +Resonant IQ does not host module runtimes or process operator conversation data. + +The proposal was removed because it violated the charter's operator-ownership principle. +It is not part of Helpthread's product or architecture. + +The original charter and its complete amendment record are preserved at +[docs/history/CHARTER-v1.md](../history/CHARTER-v1.md). + +Future material decisions should record: + +1. date and scope; +2. context; +3. decision; +4. alternatives considered; +5. consequences; and +6. superseded decisions, if any. diff --git a/docs/history/CHARTER-v1.md b/docs/history/CHARTER-v1.md new file mode 100644 index 0000000..061dd44 --- /dev/null +++ b/docs/history/CHARTER-v1.md @@ -0,0 +1,96 @@ +# Helpthread — Founding Charter + +> Helpthread is an open-source, serverless helpdesk — shared inbox, threaded email conversations, knowledge base — built for teams who live on Vercel and Supabase rather than a LAMP server. It is aimed at the bar Help Scout set for ease of use — the experience this project exists to make ownable — rebuilt in modern TypeScript with no daemons, no polling loops, and an extension system designed for how serverless software actually deploys. FreeScout, the established self-hosted PHP helpdesk, proved the self-hosted market is real; Helpthread is built for that market on modern rails. Core is AGPL-3.0; the project is dogfooded first, built by the team behind Resonant IQ as their own production support system before anyone else touches it. + +## 1. Mission & positioning + +Helpthread starts from a personal fact: its founder ran customer support on Help Scout for years and loved it. Help Scout remains the gold standard for a helpdesk that feels effortless on both sides of the conversation — and that experience is the bar this project holds itself to. What Help Scout never offered is ownership: your helpdesk, your data, your customization, on your own infrastructure, with no rent forever. Help Scout's ease, fully owned — that combination is the product. + +The self-hosted market is real: thousands of teams run FreeScout instead of paying rent to Zendesk or Help Scout. We ran it ourselves — and hit its walls. It's a decade-old Laravel 5.5 app held together by IMAP polling daemons, its extensibility means dropping PHP into a runtime plugin folder, and even basic customization — the knowledge base's design, its URLs — is out of reach without forking. Proof of the demand, and proof of the ceiling. FreeScout serves one further purpose here: because Help Scout is closed SaaS you can't inspect, FreeScout is our open *window* into that experience — a self-hostable reference for modeling the interface toward Help Scout's ease of use. It is a UX reference, never a source of code. The core is our own, built on modern, permissively-licensed foundations. + +Helpthread is the answer for people who already live on serverless infrastructure. Same ownership promise — rebuilt for a world of edge functions, managed Postgres, and push-based delivery instead of cron loops watching a mailbox. Two audiences have to come out of this feeling the same way: customers should find getting support dead easy, and operators should find running the thing dead easy. Neither of those is negotiable in favor of the other. + +## 2. Product principles + +- **Innovate on the platform. Be boringly faithful on mail semantics.** Everything about *how* Helpthread runs — compute model, storage, deployment — is fair game for rethinking from scratch. How it parses, threads, and sends email is not. Mature systems — FreeScout and Chatwoot among them — have earned a decade of edge-case scars in production email handling; we respect that earned knowledge, reproduce proven behavior, and verify against fixtures rather than re-deriving from first principles. Any change to mail behavior needs proof of equivalence or an explicit, written justification — not a hunch that the old way looked wrong. During this project's own early development we watched well-intentioned "improvements" to mail handling silently destroy message content in testing; that lesson is why this rule exists and why it's not up for debate per-PR. +- **Threading authority lives on the outbound side.** Inbound threading headers (`References`, `In-Reply-To`) are written by every mail client on earth, inconsistently — they cannot be trusted. The one header the engine fully controls is the Message-ID it emits. So threading is anchored there: signed reply tokens in outbound Message-IDs, authenticating replies on their way back in — a pattern as old as mailing-list software. This is the mechanism the whole system leans on; treat it accordingly. +- **API-first — and the UI isn't the only client.** Anything the UI can do, a typed public API can do. The UI is a client of that API, not a special case; so are AI agents. An MCP server exposing the same operations ships as a first-class client of the same API, so any operator can point their own AI tooling at their own helpdesk. +- **Serverless-native, Vercel-first, not Vercel-only.** No daemons, no polling loops. Inbound mail arrives via push webhooks (Gmail push through Pub/Sub, and equivalents as we add providers); anything that needs to happen later is a scheduled action or a cron trigger, not a process sitting in a loop. The engine's core reaches every platform service through interfaces the project owns — see the platform posture note in the architecture section. +- **Own your data.** Self-hosted on the operator's own Vercel and Supabase accounts. Conversation data never touches Resonant IQ-operated infrastructure — we don't sit in the data path — and AI features preserve that: assistants call model providers the operator configures, with the operator's own keys. That promise holds *except* where an operator explicitly opts a module into Resonant IQ's managed hosting — then that hosted module (and only it) processes the operator's conversation data and holds the operator-supplied keys it needs, on Resonant IQ infrastructure, under disclosed data-handling terms the operator consents to at enrollment; the mail engine and the system of record stay on the operator's own accounts regardless, and the self-host path keeps the original absolute promise available to anyone who wants it. *(Amended 2026-07-19 — managed hosting; see §7 appendix.)* + +## 3. Licensing & intellectual property + +Helpthread's core is licensed **AGPL-3.0**. Contributions are accepted under the **Developer Certificate of Origin (DCO)** — a sign-off on every commit, inbound license identical to outbound, no CLA, no copyright assignment. Contributors keep the copyright on their work, which makes the core's license permanent by structure rather than by promise. What makes it lawful to sell first-party commercial modules alongside the AGPL core is not ownership of the core — it is the module boundary and its plugin exception, defined below. Paid first-party modules ship under a separate proprietary/commercial license from their own closed repositories, distributed through an official marketplace — live at public launch, proven first as the project's own dogfood install path (amended 2026-07-19; see below). + +**Given up, deliberately:** dual-licensing the core — selling AGPL exceptions or relicensing later — is off the table once outside contributions land; no one can relicense the core without the consent of every contributor. That option is worth little here: the demonstrated buyers of AGPL exceptions *embed* a component in their own product, and Helpthread is an end application its users operate (running an unmodified AGPL helpdesk carries no copyleft obligations at all) — what remains, white-label resellers and blanket-AGPL-ban enterprises, is small. Also given up: a CLA's patent grant, warranties, and employer sign-offs — AGPL §11's per-contributor patent grant partially substitutes, and any indemnity offered to module customers is priced without upstream contributor representations. The revenue model is modules and the marketplace, not license arbitrage. The same structure binds Resonant IQ symmetrically: the company is a licensee of its contributors' code like anyone else, so core modifications running in any hosted offering get source-offered to its users under AGPL §13. + +**What's free and what's paid, stated early:** the core helpdesk — mail engine, conversations, agent inbox, the public API and MCP server, self-hosting — is AGPL-licensed free software, forever, and nothing free today gets paywalled retroactively. Paid, eventually: advanced first-party modules (AI assistants are the leading candidates, and the knowledge base is now one too) and possibly hosted convenience services. Monetization adds; it never subtracts. + +### Module boundary + +The line between the AGPL core and commercial modules must be an engineered artifact, not an accident. Modules — first- or third-party — integrate through a narrowly-defined, documented public plugin API, and the core's license will carry an explicit plugin exception for it (an AGPL-3.0 §7 additional permission in the Classpath-exception tradition), so module authors are never left guessing whether their work becomes a derivative of the core. Repository separation alone does no legal work — a build-time npm module compiles into the same running program as the core — so the exception text is the mechanism, and it carries the load for everyone: it covers network use (AGPL §13), not just conveyance, and it is deliberately symmetric — the same permission for first-party, third-party, and fork-based modules alike. First-party modules hold no privileged position: any hook a paid module needs ships in the public plugin API. Where a module can live out-of-process — webhooks, the event API — that is the preferred shape; it needs no exception at all. + +Under DCO the exception text is this project's real one-way door: once outside contributions merge, broadening it requires the consent of every copyright holder. Drawing the boundary precisely is therefore counsel-review work **before the first external contribution is accepted** — not before the marketplace opens — including how the exception text may evolve as the plugin API matures without ever becoming a back-door relicense of contributed code. The exception travels with every conveyed copy; a fork may keep it and grow its own module ecosystem, or may remove it as AGPL §7 permits — either way that is the accepted price and structure of the trust model, and it is why the assets that stay with the company — the Helpthread name and marks, the npm organization, the official marketplace — matter commercially. The discipline the boundary demands — narrow, stable, documented extension points — is the same discipline a good module API needs anyway. + +### Provenance + +Helpthread is an independent implementation. The repository contains no code copied or derived from any copyleft-licensed project. The core is our own code, built on permissively-licensed foundations (RFCs for mail semantics, and MIT/Apache libraries and references such as postal-mime and Chatwoot's MIT core), with each license verified at adoption. Behavior is specified and validated against public standards, public documentation, and black-box observation of running systems. Every substantive change receives real human design and review before merge — ordinary pull-request review, preserved in git history — which is also what keeps AI-assisted work firmly copyrightable under current U.S. Copyright Office guidance. Copyright we can prove we hold is what gives the project standing to enforce the AGPL — copyright claims belong to whoever owns the lines at issue — and clean title to the commercial modules. + +**Ownership, decided:** Resonant IQ, Inc. holds the copyright on its own work — pre-launch, that is the entire tree — owns the Helpthread name and marks, and will run the marketplace; Helpthread is a Resonant IQ product. Contributors keep theirs. Counsel paperwork, in deadline order: a short board consent memorializing founder alignment (before the repository goes public); the plugin exception text (before the first outside contribution is accepted — see the module boundary above); the trademark policy (before public launch). **Resolved 2026-07-10:** the contribution instrument is the DCO. The CLA that earlier drafts of this charter required is dropped — its consolidated-copyright rationale was wrong, and the relicensing power a CLA holds in reserve is precisely what the self-hosted audience this project serves has learned to distrust. + +This charter is not legal advice. Counsel reviews the licensing structure, the plugin exception, and the trademark policy, each by its deadline above. + +## 4. Architecture direction + +Serverless kills the traditional runtime plugin-folder model — there's no long-lived filesystem to drop a module into. Helpthread's extension model is designed in from day one along two tracks: **build-time npm modules**, where an operator adds a package and redeploys — the install *is* the Vercel build — and **typed event hooks / webhooks** for integrations that live out-of-process entirely. The plumbing paid modules need (license keys, license-gated distribution, an update channel) is built alongside the modules themselves and exercised through the project's own installs long before a stranger's money touches it — never as a runtime restriction: a license authenticates downloads and updates, and no shipped module ever phones home or stops working when a license lapses (`specs/modules/catalog.md` §5). + +**Amended 2026-07-19 (managed hosting):** the *default* install path is no longer the operator redeploying a module themselves — clicking Install in Manage → Modules provisions a Resonant IQ-hosted instance of the module for that deployment, the realization of the hosted convenience services §3 already contemplates. The self-host path (download the versioned tarball, deploy to your own Vercel) remains fully supported as the open-core escape hatch. Both preserve the same invariants without exception: a license is only ever a distribution credential — no runtime license check ships in any module, hosted or self-hosted, and the hosted artifact is byte-identical to the self-host tarball; no module phones home; and a lapsed license never stops running software, even when Resonant IQ is the host. The credential-bearing install/update orchestration lives entirely outside the AGPL core, in a Resonant IQ-operated hosting control plane — the one component that holds both a license key and a per-desk provisioning grant; the core holds neither and never calls the marketplace (`specs/modules/marketplace-v1.md` §3/§5). + +**Platform posture: Vercel-first, not Vercel-only.** The first-class deployment target is Vercel + Supabase, and the deploy story is optimized for it without apology. But the engine's core never calls a platform directly: queueing, scheduled and durable work, blob storage, and inbound email all sit behind thin provider interfaces the project owns, with today's implementations (Vercel Queues, Vercel Cron and Workflows, Supabase Storage, Gmail push) as adapters rather than assumptions. Inbound email forces this discipline anyway — Gmail can't be the only supported mailbox forever — and applying it to the other seams keeps a future plain-Node-plus-Postgres deployment mode reachable without an engine rewrite. Supabase itself is open source and self-hostable, so that half of the stack is a soft dependency by construction. No additional deployment targets are promised at launch: every supported target is a permanent test matrix, and that cost gets taken on only when demand justifies it. + +The conversation model is **channel-agnostic from day one**. Email is the founding channel — but a conversation and its threads don't care how a message arrived, and the schema never assumes SMTP. Chat/messaging arrives later as a second channel over the same engine (Supabase Realtime is the push transport; a "chat" is a conversation whose threads travel a faster wire), and an embeddable support widget — knowledge-base search, start a conversation, follow the replies, in the tradition of Help Scout's Beacon — is a planned first-party module built on the same public API. Live-chat trappings like presence and typing indicators layer onto that channel per-operator; they are staffing promises more than plumbing. + +The actor model is **AI-ready from day one**. Every thread records what kind of actor authored it — customer, human staff, or AI. AI-authored work supports draft-before-send states, human-approval handoffs, and a full audit trail. Like channels and modules, these are day-one schema shapes, not features: they cost a column now and a migration crisis later. Vocabulary, fixed here to prevent permanent confusion: **agents** are human support staff; **assistants** are AI actors. + +The knowledge base ships as a first-party paid module — **content-as-code** (docs live in git, build to static output, search index generated at build time) as its initial form, a runtime editor later — not as core capability. (Reclassified 2026-07-19, HT-75, reversing the original day-one core commitment; see the licensing section above and `specs/modules/catalog.md`.) + +The founding public API surface is six conversation operations: list conversations by customer email, get a conversation with its threads, get a conversation's owner, create a conversation (with attachments), add a customer reply, and look up a customer — the customer-side contract, matching what a production integration already consumes. The agent-side API grows in lockstep with the inbox UI under the API-first rule: no UI capability ships without its public API underneath. Everything else — mailbox management, roles, workflows, reporting — gets built on top of and around this surface, not ahead of it. + +## 5. Roadmap + +No dates; phases are ordered by dependency, not calendar. + +- **Phase 0 — Foundations.** Name, domains, GitHub org, and npm org secured (done). This charter. A public-ready engine repo with clean history. +- **Phase 1 — Core engine, dogfooded.** Mail engine: event-driven ingestion (bounded reconciliation fetches, never a long-running poller), parsing, threading, sending, signed reply tokens, auto-responder handling, bounce handling, HTML sanitization. The six-operation conversation API. An agent inbox UI. Gmail push for inbound. This runs as Resonant IQ's actual production support desk before it runs as anyone else's. +- **Phase 2 — Production cutover.** Resonant IQ retires its FreeScout instance and switches to Helpthread at the config level — a clean cutover with no legacy data requiring migration. +- **Phase 3 — Public launch.** Deploy-to-Vercel button, public docs site, and the start of a community — **with the marketplace live on day one**: license keys, module distribution, and the first paid modules (the knowledge base and AI-powered modules — draft-reply suggestions, auto-triage, KB-grounded auto-answers in the widget — lead the catalog), all having served as Resonant IQ's own install path through the dogfood phase. A helpdesk launched free with its commercial layer already real avoids the worse story: a community formed around ambiguity about how the project sustains itself. + +The full helpdesk surface — mailbox management, roles and permissions, workflows, SLAs, saved replies, reporting, admin, search, and the chat channel plus embeddable support widget described in the architecture section — gets built incrementally, prioritized by what real usage demands. The map of that territory is drawn from the helpdesks we know best — Help Scout's experience and FreeScout's feature surface — as a guide, not a contract; the measure Helpthread holds itself to is Help Scout's ease of use, for customers and operators alike. The mail engine, for all the care it gets, is only something like 10–15% of that eventual surface. This is a multi-month-plus endeavor, not a weekend rewrite, and nobody involved should pretend otherwise. + +## 6. Sacred invariants + +Five things we do not trade away for speed, convenience, or a good demo: + +1. Never lose or corrupt customer mail. +2. Provenance purity — no copyleft-derived code enters the shipping tree. +3. Threading correctness outranks feature velocity. +4. Main stays releasable. +5. No silent scope creep in mail semantics — see the equivalence rule in the principles. + +## 7. Governance + +Solo-maintainer, BDFL model for now — there is one project, one person accountable for it, and that's honestly where it is. Contributions are accepted under the DCO — every commit signed off, contributors keep their copyright — per the licensing section above. Conventional-commit conventions and PR review norms will get written down once there are contributors to write them down for; inventing process ahead of contributors is its own kind of scope creep, so this section stays short on purpose. + +--- + +**Status:** Written 2026-07-09, prior to the first code commit. This charter is the founding document of the Helpthread project and precedes any implementation. + +**Amended 2026-07-10 (HT-21):** §3 and §7 — the CLA/consolidated-copyright model replaced with DCO-only contributions; the module boundary's plugin exception promoted to the load-bearing legal mechanism, its counsel deadline moved to before the first external contribution; dual-licensing of the core deliberately given up; trademark policy added to pre-launch counsel work. + +**Amended 2026-07-19 (HT-79):** §3, §4, §5 — the marketplace moves from "a later phase, once demand justifies it" to a launch-day component of Phase 3, built now and proven as the project's own dogfood install path. Rationale: the original deferral assumed the module substrate and first modules would not exist until after launch; both shipped during the dogfood phase, so the remaining marketplace work is commerce plumbing, not speculation — and launching the free helpdesk with its sustainability model already visible is the more honest story. The §7 plugin exception's counsel deadline is unchanged (before first external contribution): every v1 marketplace module is out-of-process and needs no exception. New counsel items before the marketplace takes real money: the commercial module license text and terms of sale. + +**Amended 2026-07-19 (HT-79, managed hosting):** §4 — the marketplace's default install path becomes **managed hosting**: clicking Install in Manage → Modules provisions a Resonant IQ-hosted instance of the module, the realization of the "hosted convenience services" §3 already names. Decided after TJ walked the manual self-host install as customer #1 (HT-82 dogfood) and rejected its friction. The posture invariants are restated, not relaxed: (a) a license key stays a distribution credential only — no runtime license check exists in any module, hosted or self-hosted, and the hosted artifact is byte-identical to the self-host tarball; (b) a lapsed license never stops running software — a hosted instance keeps running at its entitled version, updates simply stop; (c) the AGPL core holds no license key and no marketplace credential and never calls the marketplace — the credential-bearing install/update orchestration lives entirely outside the core, in a Resonant IQ-operated hosting control plane, the only trust domain holding both license keys and per-desk provisioning grants. The self-host tarball path remains fully supported as the open-core escape hatch, and a refunded/revoked customer's already-held self-host copy keeps running with no DRM and no runtime check, ever — a consciously accepted residual exposure. Managed hosting is opt-in per operator; the core mail/data path is unchanged. Left to `specs/modules/marketplace-v1.md`, not settled by *this* amendment: the exact refund-window and config-export-grace figures, and the scoped per-desk provisioning-credential the substrate must add. The reconciliation of §2's own-your-data wording with a hosted module processing operator data on Resonant IQ infrastructure was flagged here as a data-residency call for TJ and **resolved the same day** by the own-your-data scoping amendment immediately below (HT-5/HT-82); the managed-hosting data-handling terms themselves remain on the §8 pre-revenue counsel gate. + +**Amended 2026-07-19 (HT-5/HT-82, own-your-data scoping):** §2 — the "own your data" bullet's promise is stated as *conversation data never touches Resonant IQ-operated infrastructure — we don't sit in the data path*, with one explicit, opt-in exception: a module an operator enrolls in Resonant IQ's managed hosting processes that operator's conversation data (and holds the operator-supplied keys it needs) on Resonant IQ infrastructure, under disclosed data-handling terms consented to at enrollment. This also corrects a mis-scoped earlier phrasing that put "the core" out of the data path — the operator-run core *is* the data path; the promise that matters is that **Resonant IQ-operated** infrastructure stays out of it. Rationale: the managed-hosting amendment (same date, above) made an unqualified promise falsifiable for enrolled operators; a data promise that is precisely true beats one that is absolutely worded and false in an opt-in case. The mail engine and system of record remain on the operator's own accounts in every configuration, and the self-host path preserves the original absolute promise. Decided by TJ acting as counsel, 2026-07-19; the managed-hosting data-handling terms themselves remain on the §8 pre-revenue counsel gate (`specs/modules/marketplace-v1.md` §8). + +**Amended 2026-07-19 (HT-75):** §3 and §4 — the knowledge base reclassified from free-forever core to a paid first-party module. FreeScout's own reference instance runs its Knowledge Base as a paid purchase (`specs/ui/admin-ia.md`); keeping ours core was chosen 2026-07-18 as a marketed free-vs-FreeScout differentiator (`specs/modules/catalog.md` §1), and TJ reversed that call on review. Not a retroactive paywall under the §3 invariant — the knowledge base was never shipped (`specs/modules/catalog.md` §2.1's shipped list never included it). Full paid-module catalog: `specs/modules/catalog.md`. diff --git a/docs/history/README.md b/docs/history/README.md new file mode 100644 index 0000000..ac4f3dd --- /dev/null +++ b/docs/history/README.md @@ -0,0 +1,36 @@ +# Helpthread Project History + +Helpthread began when Resonant IQ replaced its own FreeScout installation with support +software designed for the Vercel and Supabase stack it already operated. + +Help Scout established the experience bar: support should feel effortless for both the +customer and the operator. FreeScout demonstrated real demand for a self-hosted +alternative and provided an inspectable behavioral reference, but its daemon-based PHP +architecture and runtime extension model did not fit Resonant IQ's stack. + +Helpthread's founding implementation therefore combined: + +- Help Scout as a usability reference; +- FreeScout as evidence of demand and a black-box behavioral reference; +- operator choice over deployment, data, providers, and extensions; and +- serverless, push-based infrastructure with no long-running mailbox pollers. + +Helpthread is an independent implementation. Reference products inform experience and +observable behavior, never copied code. + +The project was dogfooded first as Resonant IQ's production support system. That origin +remains important, but it does not define Helpthread's permanent market or architecture. + +## Archived founding document + +[CHARTER-v1.md](CHARTER-v1.md) is the complete original charter, written on 2026-07-09 +before the first code commit. It includes the original mission, architecture, roadmap, +licensing rationale, sacred invariants, governance, and amendments through 2026-07-19. + +It was superseded by the current [Founding Charter](../../CHARTER.md) on 2026-07-23. +The archive remains historical evidence, not current authority. + +The archived July 19 managed-hosting amendment was rejected on 2026-07-23. It resulted +from a mistaken interpretation of the project direction and conflicts with operator +ownership. Helpthread and its modules are operator-deployed; Resonant IQ does not host +their runtimes or process operator conversation data. diff --git a/legal/README.md b/legal/README.md new file mode 100644 index 0000000..758666c --- /dev/null +++ b/legal/README.md @@ -0,0 +1,51 @@ +# Helpthread Legal and Licensing + +This directory holds the legal instruments and policies that implement the +[Founding Charter](../CHARTER.md). It is not legal advice; the marked drafts require +counsel review before their stated gates. + +## Core and contributions + +The Helpthread core is licensed under AGPL-3.0. Contributions use the Developer +Certificate of Origin: contributors retain copyright, the inbound license matches the +outbound license, and there is no CLA or copyright assignment. + +The project deliberately does not sell exceptions to the core license. Its commercial +model is additive software and services around the core. + +## Module boundary + +Separately licensed modules must integrate through documented public boundaries +available equally to first-party, community, and private extensions. + +For modules that form a combined program with the core, the AGPL-3.0 §7 additional +permission is the legal mechanism defining that boundary. Repository separation alone +does not do so. Out-of-process integrations generally do not need the exception. + +See: + +- [module API exception](module-api-exception.md); +- [commercial module license](module-commercial-license.md); and +- [module catalog](../specs/modules/catalog.md). + +## Provenance and trademarks + +- [Provenance policy](provenance-policy.md) records the independent-implementation, + dependency, DCO, and AI-assisted-development rules. +- [Trademark policy](trademark-policy.md) governs the Helpthread name and marks without + restricting rights granted by the software license. + +## Review gates + +Before accepting external code contributions: + +- counsel-finalize the module API exception. + +Before public launch or taking marketplace revenue, as applicable: + +- complete trademark and entity approvals; +- finalize the commercial module license and terms of sale. + +Current delivery status and outstanding work belong in [STATUS.md](../STATUS.md). +Historical rationale is indexed in +[docs/decisions/README.md](../docs/decisions/README.md). diff --git a/legal/module-api-exception.md b/legal/module-api-exception.md index 6d3d07f..4068a4a 100644 --- a/legal/module-api-exception.md +++ b/legal/module-api-exception.md @@ -1,14 +1,14 @@ # Helpthread Module API Exception — AGPL-3.0 §7 Additional Permission **Status: DRAFT for counsel review (TJ, acting as counsel). Not yet adopted.** -Adoption gate (CHARTER.md §3): this text must be final and applied to the core's +Adoption gate (see [the legal guide](README.md)): this text must be final and applied to the core's license **before the first external contribution is merged** — under DCO, broadening it afterward requires the consent of every copyright holder. Adoption mechanics: on sign-off, the exception text below is appended to the repository's `LICENSE` file beneath the AGPL-3.0 text, referenced from every source-file header block that names -the license, and noted in CHARTER.md's §7 appendix with the adoption date. +the license, and recorded in this directory with the adoption date. -> **Provenance note (added 2026-07-20, HT-100).** This draft was authored by an assistant, +> **Provenance note (added 2026-07-20).** This draft was authored by an assistant, > reviewed by an assistant, and merged in PR #99 with **zero human review comments** — 1h37m > from open to merge, inside a window with no human input at the decision point. The > "independent different-vendor review" comments on that PR were posted by the assistant @@ -22,7 +22,7 @@ the license, and noted in CHARTER.md's §7 appendix with the adoption date. Drafting tradition: the GPL Classpath Exception and the FSF's §7 additional-permission mechanism, adapted for (a) AGPL §13 network use, which the Classpath exception predates, and (b) Helpthread's build-time npm module model, where a module compiles into the same -running program as the core (CHARTER.md §3: "Repository separation alone does no legal +running program as the core ([legal guide](README.md): "Repository separation alone does no legal work"). Symmetric by design: the same permission for first-party, third-party, and fork-based modules alike. diff --git a/legal/module-commercial-license.md b/legal/module-commercial-license.md index b85adb2..561ef5c 100644 --- a/legal/module-commercial-license.md +++ b/legal/module-commercial-license.md @@ -5,8 +5,8 @@ > not been reviewed by outside counsel — TJ is the reviewing counsel. > > **Gate:** must clear review **before public launch** — specifically before Stripe is -> switched to live mode and the marketplace takes real money. It does **not** gate the -> HT-82 test-mode dogfood, where a placeholder license suffices (`specs/modules/ +> switched to live mode and the marketplace takes real money. It does **not** gate +> test-mode dogfood, where a placeholder license suffices (`specs/modules/ > marketplace-v1.md` §8). On adoption, this text **replaces the `All rights reserved` > placeholder `LICENSE`** in each paid-module repository. @@ -15,29 +15,23 @@ ## 1. Parties and subject This Helpthread Commercial Module License (the **"License"**) is a legal agreement -between **Resonant IQ, Inc.**, a Delaware corporation (**"Resonant IQ"**, **"we"**, or -**"us"**, the licensor), and the individual or entity that purchases a subscription to a -Helpthread commercial Module (**"you,"** the **"Licensee"**). +between **Resonant IQ, Inc.**, a Delaware corporation (**"Resonant IQ,"** **"we,"** or +**"us,"** the licensor), and the individual or entity that purchases a subscription to a +Helpthread commercial module (**"you,"** the **"Licensee"**). A **"Module"** is a first-party Helpthread extension that Resonant IQ distributes for a fee through the official Helpthread marketplace, delivered to you as **Source** — a source-code tarball, not a compiled binary. The **"Source"** is the complete tarball contents of a Module release as published. This License governs your use of the Module and its Source. It does **not** govern the Helpthread core, which is separately licensed -under the GNU Affero General Public License, version 3.0 (**"AGPL-3.0"**), together with -any additional permissions the core's LICENSE file carries (including, once adopted, the -Helpthread Module API Exception), nor any third-party dependency, which each carry their -own licenses. The Source may include third-party materials under their own permissive -licenses; their notices travel with the Source as delivered. Your rights in the Module are +under the GNU Affero General Public License, version 3.0 (**"AGPL-3.0"**), nor any +third-party dependency, which each carry their own licenses. Your rights in the Module are exercised through the documented module interfaces and out-of-process integration; nothing in this License restricts, modifies, or replaces any right you have in the AGPL-licensed core under the AGPL-3.0. -Your rights under this License **to receive downloads and updates** begin when your -subscription is active. Your rights in copies you have already received are governed by -the Surviving Held-Copies License (section 7), which continues as stated there -**regardless of subscription status** — nothing in this section, or in any statement -that rights "begin" with an active subscription, cuts back what section 7 grants. +Your rights under this License begin when your subscription is active and are subject to +the survival and termination terms in sections 7 and 8. ## 2. Subscription @@ -45,11 +39,6 @@ The Module is licensed, not sold, on an **annual subscription** basis. A subscri grants the rights in section 4 for the paid term and entitles you to Module updates as described in section 6 for as long as the subscription remains in good standing. -Subscription fees are **exclusive of taxes**. You are responsible for any sales, use, -value-added, or similar taxes arising from your purchase, other than taxes on Resonant -IQ's income; where Resonant IQ is required to collect them, they are added at checkout -as stated in the terms of sale. - ## 3. Licensing unit — one license, one domain The unit of licensing is the **domain**. One license authorizes the use of one Module in @@ -58,20 +47,16 @@ the Module for more than one domain requires a separate license — and therefor separate subscription — for each additional domain. This is a **contractual term**, and it is enforced solely by this License. Consistent -with section 7, Resonant IQ does **not** and will not verify, meter, or technically +with section 5, Resonant IQ does **not** and will not verify, meter, or technically constrain the number of domains on which the Module runs; the Module contains nothing that records, reports, or checks the Licensed Domain. Honoring the one-license-per-domain term is your contractual obligation, not a gate the software imposes. ## 4. What you may do -Resonant IQ grants you a **non-exclusive, non-transferable** (except as section 13 -allows), **non-sublicensable** license, for the term stated in sections 1, 2, and 7, -to do the following. Subject to sections 3 and 5, while your subscription is active -you may: +Subject to sections 3 and 5, while your subscription is active you may: -1. **Run** the Module in your helpdesk deployment for the Licensed Domain, whether you - self-host it on your own infrastructure or run it through Resonant IQ managed hosting. +1. **Run** the Module in your operator-controlled Helpthread deployment for the Licensed Domain. 2. **Read** the Source in full. 3. **Modify** the Source **for your own internal use** — to adapt, configure, fix, or extend the Module for your own Licensed Domain deployment. @@ -118,20 +103,6 @@ run indefinitely, per section 7. Resuming payment restores access to current ver A lapse is an ordinary non-payment event; it is not a finding of wrongdoing and carries no penalty beyond pausing access to **new** releases. -**A lapse does not stop a Resonant IQ-hosted instance.** If Resonant IQ hosts your -Module instance, that instance **keeps running throughout any lapse, at the version you -were entitled to at the moment of lapse**; it is **not** decommissioned for non-payment, -and it continues to serve your deployment until you cancel it or one of the section 8 -events that ends hosting — a **full refund** or a **revocation for confirmed fraud** — -occurs. What a lapse stops is updates, nothing else. - -**License keys are distribution credentials only.** Any license key, token, or other -credential Resonant IQ issues under this License authenticates marketplace downloads and -the update channel, and does nothing else. It is not required to run the Module, is never -checked at runtime by the Module or the helpdesk core, and has no effect on copies you -already hold. Disabling, rotating, or revoking a key affects marketplace download and -update access only. - ## 7. Your held copies keep working — the Surviving Held-Copies License This is a deliberate and permanent commitment, stated affirmatively because it is part @@ -164,14 +135,10 @@ update channel**, never software already in your hands: versions you were entitled to at the moment of lapse. - A **termination** — whether by full refund or by revocation for fraud (section 8) — ends **all** future marketplace download access, including to versions you were - previously entitled to, and any instance Resonant IQ hosts on your behalf is - decommissioned in accordance with the published managed-hosting policy. The - configuration-export grace window applies to termination by **full refund**; - revocation for confirmed fraud decommissions immediately, with no grace window - (section 8). Termination never reaches any - copy of the Module, original or modified, that you already hold and run on your own - infrastructure: those copies continue under the Surviving Held-Copies License above, - because there is nothing in them to switch off. + previously entitled to. Termination never reaches any copy of the Module, original or + modified, that you already hold and run on your own infrastructure: those copies + continue under the Surviving Held-Copies License above, because there is nothing in + them to switch off. This guarantee is why the licensing unit in section 3 can rest on contract alone: we would rather state the terms plainly and trust you to honor them than degrade the @@ -181,7 +148,7 @@ product with enforcement machinery that would make it hostile to the people who Termination under this section ends the **forward entitlement** this License grants — your right to new downloads and updates, all future marketplace download access (including to -versions you were previously entitled to), and any Resonant IQ-hosted service. It does +versions you were previously entitled to). It does **not** disturb the **Surviving Held-Copies License** in section 7, which is a separate, defined right that continues for the copies already in your hands and your internal modifications of them. "Terminated" and "survives" therefore refer to two different things: @@ -192,43 +159,23 @@ the marketplace **terms of sale** (fourteen (14) days from purchase; only a refu full purchase price terminates — partial or goodwill refunds do not affect this License), the forward entitlement under this License terminates: your entitlement to new downloads and updates ends, and all future marketplace download access ends, including to versions -you were previously entitled to. A Resonant IQ-hosted instance is decommissioned after the -configuration-export grace period stated in those terms (seven (7) days), during which you -may export the instance's configuration. Section 7 governs what the termination does -**not** reach. +you were previously entitled to. Section 7 governs what the termination does **not** +reach. **Revocation for fraud.** Resonant IQ may revoke this License for confirmed fraud — for -example a stolen payment method or a fraudulent chargeback — following an actual -investigation. Revocation ends the forward entitlement and all future marketplace download access -(including to previously entitled versions) and, for a hosted instance, results in -immediate decommissioning. Revocation is never triggered automatically by a payment +example a stolen payment method, a fraudulent chargeback, or redistribution or resale of +the Source in knowing breach of section 5 — following an actual investigation. +Revocation ends the forward entitlement and all future marketplace download access +(including to previously entitled versions). Revocation is never triggered automatically by a payment dispute merely being filed. Section 7 again governs what revocation does **not** reach. -**Ordinary breach is not fraud.** A non-fraudulent breach of this License or the terms -of sale — running a second deployment on a single-domain License, say — is **not** -grounds for revocation under this section. Revocation is reserved for confirmed fraud, -because its consequences (immediate loss of download access and decommissioning of a -hosted instance) are the harshest this License provides. Ordinary breach is addressed by -the following paragraph, and commercially under the terms of sale. - -**Termination of forward entitlement for material breach.** If you materially breach -section 3 or section 5 and the breach is not fraud, Resonant IQ may give you written -notice describing the breach. If the breach is not cured within **thirty (30) days** of -that notice (or, where the breach is incapable of cure — a completed public disclosure of -the Source, say — immediately upon notice), Resonant IQ may terminate your **forward -entitlement**: new downloads, updates, and managed hosting of new instances end. This -remedy does **not** reach the Surviving Held-Copies License (section 7), does not -decommission an already-running hosted instance, and is in addition to — not in place of -— any claim for damages or injunctive relief Resonant IQ may have for the breach itself, -subject to section 10. - -The full mechanics and exact time windows of refunds, disputes, and hosted-instance -decommissioning are set out in the marketplace **terms of sale** and **managed-hosting -terms**, and are not restated in full here; this section states only their effect on -this License. - -Sections 3 (as it applies to copies held under section 7), 5, 7, 9, 10, 11, 12, and 13 -survive termination of this License. +**Ordinary breach is not fraud.** A non-fraudulent breach of the terms of sale — running +a second deployment on a single-domain License, say — is **not** grounds for revocation +under this section. Revocation is reserved for confirmed fraud because loss of download +access is the harshest consequence this License provides. Ordinary breach is a commercial +matter handled under the terms of sale. + +Sections 5, 7, 9, 10, 11, 12, and 13 survive termination of this License. ## 9. Warranty disclaimer @@ -240,10 +187,6 @@ non-infringement, and any warranty arising from course of dealing or usage of tr Resonant IQ does not warrant that the Module will be uninterrupted, error-free, or free of harmful components, or that it will meet your requirements. -**No support obligation.** This License itself includes no technical support, -maintenance, or service-level commitment. Any support Resonant IQ offers is described -in the terms of sale or a separate agreement, not here. - ## 10. Limitation of liability To the maximum extent permitted by applicable law, Resonant IQ will not be liable for any @@ -259,7 +202,7 @@ in the twelve (12) months** immediately preceding the event giving rise to the c **No indemnity based on upstream contributor representations.** Resonant IQ offers no indemnification obligation predicated on representations, warranties, or covenants of third-party or community contributors to the Helpthread project. Consistent with the -Helpthread charter's licensing structure (CHARTER.md §3), the project accepts +Helpthread's licensing structure (see the [legal guide](README.md)), the project accepts contributions under the Developer Certificate of Origin without a contributor license agreement, and therefore without the warranties or indemnities a CLA would collect; any indemnity Resonant IQ may separately agree to is priced and scoped without reliance on @@ -274,9 +217,8 @@ Secretary of State May 19, 2026, file no. 10629316 — corporate records on file ## 12. Entire agreement and order of precedence -This License, together with the marketplace terms of sale and — where you use managed -hosting — the managed-hosting terms and data-handling disclosure, constitutes the entire -agreement between you and Resonant IQ regarding the Module, and supersedes any prior or +This License, together with the marketplace terms of sale, constitutes the entire +agreement between you and Resonant IQ regarding the Module and supersedes any prior or contemporaneous understandings on that subject. Each of these documents governs its own domain, and on any conflict each controls within @@ -286,9 +228,6 @@ that domain: the Module and its Source (sections 4, 5, and 7). - The marketplace **terms of sale** control the **commerce** — pricing, refunds, lapse, disputes, and the domain-count economics of purchasing. -- The **managed-hosting terms** (and data-handling disclosure) control **hosted - operations** — how a Resonant IQ-hosted instance is run, decommissioned, and its data - handled. Where the documents appear to conflict, the one whose domain the matter falls into controls for that matter; no document is read to override another outside its own domain. @@ -307,11 +246,8 @@ controls for that matter; no document is read to override another outside its ow - **Assignment; change of control.** You may not assign or transfer this License, in whole or in part, without Resonant IQ's prior written consent, except that this License transfers with a sale of all or substantially all of the licensee's business or assets to - which the Licensed Domain belongs, for continued use by the acquired operations, on - thirty (30) days' written notice to Resonant IQ. If those operations migrate to a - different domain, the acquirer may redesignate the Licensed Domain once as part of the - transfer — the License still covers exactly **one** domain (section 3); redesignation - changes which one, not how many. Any other purported assignment is void. Resonant IQ may + which the Licensed Domain belongs, for continued use on that same Licensed Domain, on + written notice to Resonant IQ. Any other purported assignment is void. Resonant IQ may assign this License in connection with a merger, acquisition, or sale of its business. --- @@ -322,15 +258,11 @@ controls for that matter; no document is read to override another outside its ow filed Certificate of Incorporation (Delaware SoS, filed May 19, 2026, file no. 10629316) in the company's corporate records. Venue/forum-selection remains undrafted (outside the decided scope) — add if desired. -- **Cross-references left as pointers.** The refund window (14 days), full-refund-only - termination, and the config-export grace window (7 days) are referenced as - **placeholders**, with their mechanics deferred to the terms of sale. - **⚠️ These figures were NOT decided by TJ.** An earlier draft cited them as "CONFIRMED - by TJ 2026-07-19"; an audit on 2026-07-20 searched every message he sent and found no - instance of any of them. They were generated by an assistant and cited back as his. - They stand as placeholders until he states the figures himself. +- **Cross-references left as pointers, by instruction.** The refund window (14 days) + and full-refund-only termination are referenced with their decided values, while + their full mechanics remain in the terms of sale rather than being restated here. - **Scope held to the decided space.** Multi-domain / bulk licensing is deliberately not drafted (decision: "may exist later; do not draft it"). Severability, waiver, notices, and assignment/change-of-control were added per the adjudicated Codex-review fixes - (2026-07-19, HT-5); no arbitration clause and no venue/forum-selection clause beyond the + (2026-07-19); no arbitration clause and no venue/forum-selection clause beyond the Delaware governing law in section 11 is included. Export-control remains undrafted. diff --git a/legal/provenance-policy.md b/legal/provenance-policy.md index 6b7c904..599e52d 100644 --- a/legal/provenance-policy.md +++ b/legal/provenance-policy.md @@ -1,7 +1,7 @@ # Provenance & AI-Assisted Development Policy (DRAFT) > **Status: DRAFT for counsel review (TJ).** Drafted 2026-07-19. This is a faithful -> first draft codifying practices already stated in CHARTER.md §3; it is not legal advice +> first draft codifying practices already stated in the [legal guide](README.md); it is not legal advice > and has not been reviewed by outside counsel — TJ is the reviewing counsel. > > **This document states policy and process — the commitments and practices the project @@ -22,7 +22,7 @@ project's ability to enforce the AGPL on its core (you can only enforce a copyri prove you hold), supports Resonant IQ's title to its commercial modules, and is intended to support the copyrightability of AI-assisted work under current U.S. Copyright Office guidance. This policy states, as our standing practice, how the Helpthread codebase is -built so that its provenance stays clean and demonstrable. It codifies what CHARTER.md §3 +built so that its provenance stays clean and demonstrable. It codifies what the [legal guide](README.md) (Provenance) already commits the project to; where this policy and the charter appear to differ, the charter governs. @@ -35,21 +35,19 @@ project's own code, built on permissively-licensed foundations, and our practice specify its behavior from first principles against public sources rather than reproduce another project's implementation. -This is one of the project's **sacred invariants** (CHARTER.md §6): *provenance +This is one of the project's **project commitments** (CHARTER.md, "Provenance must be defensible"): *provenance purity — no copyleft-derived code enters the shipping tree.* ## 3. What we read, and what we do not -The distinction between a **behavioral reference** (which informs *what* the software -should do) and a **code source** (from which implementation is taken) is load-bearing, -and the project holds it strictly. +The distinction between **observed behavior** (evidence about how a system can behave) +and a **code source** (from which implementation is taken) is load-bearing, and the +project holds it strictly. Observations do not define Helpthread's product decisions. **Our policy is that copyleft codebases are not opened during development.** -Copyleft-licensed source — FreeScout's AGPL source foremost among the references the -project is otherwise aware of — is not consulted while building Helpthread. FreeScout -serves only as a *window into the user experience* of a self-hosted helpdesk; it is a UX -and feature-surface reference, never a source of code, and our practice is not to consult -its source. +Copyleft-licensed source is not consulted while building Helpthread. Historical +black-box observations of running systems may remain as evidence, but they do not +govern Helpthread's interface, feature set, or commercial boundaries. **Where behavior comes from instead:** @@ -94,7 +92,7 @@ it is merged** — ordinary pull-request review, preserved in git history. This first-class practice, not a formality: - it is how threading correctness, mail-semantics equivalence, and the other sacred - invariants are actually protected (CHARTER.md §6); + invariants are actually protected (CHARTER.md, "Conversation integrity" and "Provenance must be defensible"); - it is the human authorship and creative control that, under current U.S. Copyright Office guidance on AI-assisted works, is **intended to support the copyrightability** of the resulting code — a rubber stamp would not meet that bar; @@ -106,7 +104,7 @@ first-class practice, not a formality: Contributions are accepted under the **Developer Certificate of Origin (DCO)** — a sign-off on **every commit**, with the inbound license identical to the outbound license -and no contributor license agreement (CHARTER.md §3, §7). The DCO sign-off is each +and no contributor license agreement (see the [legal guide](README.md)). The DCO sign-off is each contributor's attestation that they have the right to submit the work under the project's license. Contributors keep the copyright on their own work; Resonant IQ holds the copyright on its own. @@ -139,18 +137,17 @@ AI-assisted development is assessed and managed under internal counsel review. ### Notes to counsel (not part of the policy) -- **Faithful to CHARTER.md §3.** Sections 2–7 restate practices the charter already +- **Faithful to the [legal guide](README.md).** Sections 2–7 restate practices the charter already commits to (independent implementation; no copyleft-derived code; copyleft source never opened; behavior from RFCs / public docs / black-box; permissive references — postal-mime MIT-0, Chatwoot MIT core with `enterprise/` excluded; license verification at adoption; human review as the copyrightability basis; DCO on every commit). No new policy is invented. - **AI-training-data risk moved to a private counsel memo.** Per the adjudicated - Codex-review fixes (2026-07-19, HT-5), the candid residual-risk analysis that was §8 is + Codex-review fixes (2026-07-19), the candid residual-risk analysis that was §8 is no longer in this public file; §8 now states only that the risk is assessed and managed under internal counsel review. The full analysis and mitigations live in a privileged counsel memo held outside the repository. -- **Reference-list scope.** FreeScout is named only as the UX/experience window it is - (never a code source); the "look-only" posture for reference projects whose license - doesn't resolve is carried from the charter and CLAUDE.md. Specific such projects are - not named here to avoid dating the policy; counsel can add or omit names as desired. +- **Reference-list scope.** The "look-only" posture for projects whose license does not + resolve is carried from the charter and CLAUDE.md. Specific such projects are not named + here to avoid dating the policy; counsel can add or omit names as desired. diff --git a/legal/trademark-policy.md b/legal/trademark-policy.md index b05451d..9b4ba20 100644 --- a/legal/trademark-policy.md +++ b/legal/trademark-policy.md @@ -4,7 +4,7 @@ > first draft encoding already-made decisions; it is not legal advice and has not been > reviewed by outside counsel — TJ is the reviewing counsel. > -> **Gate:** must clear review **before public launch** (CHARTER.md §3 lists "the +> **Gate:** must clear review **before public launch** (the [legal guide](README.md) lists "the > trademark policy (before public launch)" among the counsel paperwork in deadline > order). @@ -159,14 +159,14 @@ beyond those described here. Write to **trademarks@resonantiq.app**. ### Notes to counsel (not part of the policy) -- **Owner / entity.** Marks attributed to Resonant IQ, Inc. per CHARTER.md §3 +- **Owner / entity.** Marks attributed to Resonant IQ, Inc. per the [legal guide](README.md) ("owns the Helpthread name and marks"). The npm org and official marketplace name are named as Marks per the charter's "the assets that stay with the company — the Helpthread name and marks, the npm organization, the official marketplace." - **Rename-on-material-modification** is drafted in the Firefox/Iceweasel tradition and stated politely, as instructed — a request and a courtesy, not an AGPL restriction (which would be void under the AGPL's own terms). Per the adjudicated Codex-review fixes - (2026-07-19, HT-5), §4 now (a) states expressly that it is not a condition of the AGPL + (2026-07-19), §4 now (a) states expressly that it is not a condition of the AGPL and imposes no copyright-license restriction, and (b) defines "materially modified" on a confusion-based standard with de minimis examples (security patches, packaging, config defaults, backports = not material). diff --git a/package.json b/package.json index 953486d..69936d0 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": false, "type": "module", "license": "AGPL-3.0-only", - "description": "Open-source, serverless helpdesk engine — shared inbox, threaded email, knowledge base — for teams who live on Vercel and Supabase.", + "description": "Open-source support infrastructure.", "engines": { "node": ">=20" }, diff --git a/specs/api/agent-inbox-v1.md b/specs/api/agent-inbox-v1.md index b152ec3..8798b5c 100644 --- a/specs/api/agent-inbox-v1.md +++ b/specs/api/agent-inbox-v1.md @@ -1,17 +1,17 @@ # Agent Inbox API v1 -Status: accepted (HT-17 reads, HT-18 writes, HT-16 send idempotency); **amended to v1.1** -(HT-25, 2026-07-11) — the contract additions the Agent Inbox UI was designed against, +Status: accepted; **amended to v1.1 on 2026-07-11** — the contract additions +the Agent Inbox UI was designed against, adopted as the v1 build target (§7 changelog). Helpthread's first public API, designed **native** — on Helpthread's own domain model, not reverse-engineered from any other helpdesk's wire format. (It supersedes the earlier `conversations-v1.md` draft, which was -shaped for a FreeScout-consumer cutover that no longer applies — see the project history.) +shaped for a legacy-consumer cutover that no longer applies — see the project history.) ## 1. Purpose This is the **Agent side**: the surface an Agent (today, a single operator) uses to work the inbox — see what has come in, read a conversation, and act on it. It is the API under -the Agent inbox UI (API-first, CHARTER.md §2), and the loop Helpthread is dogfooded +the Agent inbox UI (the charter's "Public APIs and events" rule), and the loop Helpthread is dogfooded through: mail lands → Agent sees it → Agent replies. v1 is deliberately single-Agent: there is no per-Agent identity, no teams. The Bearer @@ -20,17 +20,17 @@ token authenticates *the deployment's one operator*, not a user among many. The inbox's "Mine" folder works without inventing users. Multi-Agent identity is a later increment, added when there is a second Agent. -This document covers the whole v1 surface. **HT-17 implemented §3's read paths and the -conventions below; HT-18 implemented §4a–4b; HT-16 amended §4a with send idempotency; HT-49 -amended §4a's `References` derivation to append the reply's own minted id (a provider — -Gmail, confirmed live — can rewrite `Message-ID` on send; threading.md §2a).** -The v1.1 additions land per-ticket: HT-26 (status model), HT-27 (`preview` + `number`), -HT-28 (notes), HT-29 (tags), HT-30 (delete), HT-31 (assignee), HT-32 (open tracking). +This document covers the whole v1 surface. The read paths and conventions are in §3; +write paths are in §4; §4a includes send idempotency and appends the reply's own minted +id to `References` because a provider—Gmail, confirmed live—can rewrite `Message-ID` +on send (see threading.md §2a). +The v1.1 additions include the status model, `preview` and `number`, notes, tags, +deletion, assignment, and open tracking. -Rollout note: **HT-26 is the one BREAKING increment** — existing status values are +Rollout note: **the status-model change is the one breaking increment** — existing status values are renamed and the list filter's meaning changes, so backend and UI adopt it together (a coordinated rollout, deliberately first in the sequence; dogfood-only means the -coordination is a single deploy, per HT-16's same reasoning). Every OTHER addition is +coordination is a single deploy). Every other addition is additive with a nullable/empty default, and the UI degrades per-field for those — partial deployment of the additive increments is safe. @@ -41,7 +41,7 @@ surfaced as JSON with ISO-8601 timestamps and no translation layer. ```ts type ConversationStatus = 'active' | 'pending' | 'closed' | 'spam' - // v1.1 (HT-26). 'deleted' is never surfaced (§3a) + // v1.1. 'deleted' is never surfaced (§3a) interface ConversationSummary { id: string // uuid — the canonical id, used verbatim in every path @@ -51,16 +51,16 @@ interface ConversationSummary { status: ConversationStatus threadCount: number assignee: 'me' | null // v1.1: null = Anyone; single-Agent shaped (§4f) - tags: string[] // v1.1: short lowercase labels, [] default (§4e) + tags: string[] // v1.1: short lowercase labels, [] default (§4e) preview: string // v1.1: latest bodyText excerpt, '' when none (derivation below) - snoozedUntil: string | null // v1.1 (HT-77): ISO-8601, a timed `pending`; always null for + snoozedUntil: string | null // v1.1 : ISO-8601, a timed `pending`; always null for // every other status — see §4b's snooze amendment createdAt: string // ISO-8601 updatedAt: string // ISO-8601 — last activity; the inbox sort key } interface ConversationDetail extends ConversationSummary { - threads: ThreadView[] // oldest-first + threads: ThreadView[] // oldest-first } interface ThreadView { @@ -72,7 +72,7 @@ interface ThreadView { bodyText: string | null bodyHtml: string | null // ⚠ UNTRUSTED, UNSANITIZED — see §5 deliveryStatus: 'pending' | 'sent' | 'failed' | null - // outbound only; null otherwise. HT-70: the invariant widens + // outbound only; null otherwise. : the invariant widens // — an outbound thread's deliveryStatus is ALSO null while it // is an unapproved or discarded draft (draftStatus below is // 'awaiting_review' or 'discarded'); a draft becomes eligible @@ -82,18 +82,18 @@ interface ThreadView { // enabled (§4g) — first time the customer viewed the reply; // null until then, always null for inbound and notes attachments: AttachmentView[] - // HT-46: inbound attachments this thread carries. [] when + // inbound attachments this thread carries. [] when // there are none, OR when the deployment hasn't wired the // attachment read-path deps (config-gated, absent by default // — same posture as open tracking, §4g) createdAt: string // ISO-8601 authorKind: 'customer' | 'agent' | 'assistant' - // HT-70 (specs/plugins/substrate-v1.md §2, §7): who authored + // (specs/plugins/substrate-v1.md §2, §7): who authored // this thread — 'customer' for inbound mail, 'agent' for // human-authored outbound/notes, 'assistant' for an // AI-authored draft (specs/plugins/substrate-v1.md §3, §6) draftStatus: 'awaiting_review' | 'approved' | 'discarded' | null - // HT-70: a draft's lifecycle state; null for every non-draft + // A draft's lifecycle state; null for every non-draft // thread (specs/plugins/substrate-v1.md §2, §6) } @@ -106,7 +106,7 @@ interface AttachmentView { } ``` -**Status semantics (v1.1, HT-26).** `active` is the working state — inbound mail creates +**Status semantics (v1.1).** `active` is the working state — inbound mail creates conversations `active`, and v1.0's `open` rows migrate to `active`. `pending` is an Agent statement that the conversation is parked awaiting something outside the inbox (a customer, a third party, a release); nothing sets it automatically in v1, and it still @@ -114,10 +114,10 @@ counts as open work (§3a). `closed` is resolved. `spam` is junk an Agent has th of the inbox; nothing classifies spam automatically in v1. Status pills in the UI: Active = accent, Pending = warn, Closed = dim, Spam = critical. -**Snooze exception to "pending is never cleared automatically" (v1.1, HT-77).** A snooze +**Snooze exception to "pending is never cleared automatically" (v1.1).** A snooze is a TIMED `pending` — `pending` plus a `snoozedUntil` timestamp (§4b) — and it is the ONE case where `pending` clears itself: a periodic wake pass flips a snoozed conversation -`pending` → `active` (clearing `snoozedUntil`) once `now() >= snoozedUntil`, with no Agent +`pending` → `active` (clearing `snoozedUntil`) once `now >= snoozedUntil`, with no Agent action. Inbound mail on a snoozed conversation ALSO wakes it early, the same way inbound mail reopens a `closed`/`spam` conversation (§4a). **Plain `pending` — no `snoozedUntil` — is unaffected: it still stays `pending` forever until an Agent changes it, exactly as @@ -125,7 +125,7 @@ originally specified.** The snooze exception is additive and narrowly scoped to opted in with a timestamp; it does not change plain-pending semantics at all. **`number`** is assigned from a per-deployment monotone sequence at conversation -creation (existing rows are backfilled in creation order by the HT-27 migration). It +creation (existing rows are backfilled in creation order by the relevant migration). It exists for humans — inbox rows, notifications, "re: #482" in conversation — and is display-only: every path parameter remains the uuid, and `number` is never accepted as an identifier anywhere in this API. @@ -133,7 +133,7 @@ an identifier anywhere in this API. **`preview`** is derived at read time, not stored: the most recent thread with a non-null `bodyText` (any direction — notes included; this is an Agent-only surface), whitespace collapsed to single spaces, trimmed, first 120 characters; `''` when no -thread has text. **HT-70:** `preview` and `threadCount` both IGNORE an unresolved or +thread has text. **Draft handling:** `preview` and `threadCount` both ignore an unresolved or discarded draft (`draftStatus IN ('awaiting_review', 'discarded')`) — a draft is not conversation content until an Agent approves it, so it contributes to neither the count nor the latest-body derivation. An `'approved'` draft (i.e. sent mail) counts and @@ -158,10 +158,10 @@ added when a real need appears, not preemptively. wrong token is `401 unauthorized` with a generic message — the response never reveals which of those it was. (The open-tracking pixel, §4g, is the one deliberate exception to Bearer auth — it is fetched by customer mail clients and carries its own rules.) - **This is still the API's only auth model — with one addition (HT-70).** The Agent + **This is still the API's only auth model — with one addition.** The Agent Inbox web app now requires an operator to sign in before it will render any page, but that is a web-layer door in front of this same Bearer token, not a second API auth - mechanism — see §5 for the full justification. HT-70 (specs/plugins/substrate-v1.md + mechanism — see §5 for the full justification. (specs/plugins/substrate-v1.md §3) DOES add a genuine second credential class, checked ALONGSIDE the service Bearer token, never replacing it: a per-Assistant token (`ht_asst__`), verified before routing under the same constant-time discipline (parse the embedded @@ -180,7 +180,7 @@ added when a real need appears, not preemptively. user-safe and MUST NEVER contain an internal detail — no stack, no SQL, no upstream body, no id it wasn't given. HTTP status pairs with `code`: 400 `validation_failed`, 401 `unauthorized`, 404 `not_found`, 405 `method_not_allowed`, 409 `retry_in_progress` (§4a, - HT-16 — a concurrent delivery attempt for the same `Idempotency-Key` already holds the + — a concurrent delivery attempt for the same `Idempotency-Key` already holds the lease), 500 `server_error`, 502 `send_failed` (§4a, the provider rejected an outbound reply). - **Unknown routes / methods:** an unmatched path is `404 not_found`; a known path with an @@ -217,7 +217,7 @@ Returns a `ConversationDetail` — the conversation plus its `threads`, oldest-f not_found` if `{id}` is not a conversation (or is a `deleted` one — a deleted conversation is indistinguishable from a nonexistent one to this API, on purpose). -**HT-70:** `threads` includes draft rows (`draftStatus` non-null) for Agent/service +**Draft handling:** `threads` includes draft rows (`draftStatus` non-null) for Agent/service callers, at every lifecycle stage — the timeline shows an `awaiting_review`/`discarded` draft alongside real mail, distinguishable by `authorKind: 'assistant'` and `draftStatus`. An Assistant caller reads the same endpoint and sees its own drafts @@ -229,9 +229,9 @@ through it too (no separate read surface). Only the summary-level `preview`/ ### 4a. `POST /api/v1/conversations/{id}/replies` — the Agent replies -**Header:** `Idempotency-Key` is **REQUIRED** on every call (HT-16) — a non-empty, +**Header:** `Idempotency-Key` is **REQUIRED** on every call — a non-empty, caller-chosen string, scoped per-conversation. This is a deliberate breaking change from -the HT-15 shape of this endpoint; it has no external consumer yet (this API is +the earlier shape of this endpoint; it has no external consumer yet (this API is dogfood-only — CHARTER.md "dogfooded first"), so tightening the contract here has no compatibility cost. The header is **trimmed of leading/trailing whitespace before any other check**, so `" key "` and `"key"` are the same idempotency key — a caller whose @@ -242,7 +242,7 @@ header that is empty (or all whitespace) after trimming, or a trimmed value over characters is `400 validation_failed`, checked before the body is parsed. Body: `{ text: string; html?: string; thenSetStatus?: 'closed' | 'pending' }` — `text` -1–5000 chars, server-enforced; `html` optional; `thenSetStatus` optional (v1.1, HT-78, +1–5000 chars, server-enforced; `html` optional; `thenSetStatus` optional (v1.1, "Send & Close" — see below). The Agent supplies only the message; every mail header is DERIVED server-side from the conversation, so the client never sets recipients or threading headers: @@ -261,7 +261,7 @@ threading headers: omitted the way `In-Reply-To` can be). These are for the customer's mail client to thread the reply in THEIR inbox — Helpthread's own threading never depends on them (it is outbound-token-anchored; threading.md §2) — but the reply's own minted id riding in - `References` is now load-bearing in one specific way (HT-49, threading.md §2a): some + `References` is now load-bearing in one specific way (threading.md §2a): some providers (Gmail, confirmed live) rewrite the wire `Message-ID` to their own generated id, so `References` — which such providers do NOT rewrite — is the channel that actually gets the signed token back into the customer's reply when that happens. @@ -272,7 +272,7 @@ new send), persists the outbound thread with a snapshot of its envelope (`send_envelope`: `to`/`cc`/`subject`/`references`, `sending.md` §3a), and sends via the injected `EmailSender`. -**`thenSetStatus` — "Send & Close" (v1.1, HT-78).** An optional `'closed' | 'pending'` in +**`thenSetStatus` — "Send & Close" (v1.1).** An optional `'closed' | 'pending'` in the request body. When present, the conversation's status is ALSO set to it, applied in the SAME database transaction as the reply's persist, immediately after it — BEFORE the network send, matching this endpoint's existing persist→send→mark ordering (the status @@ -318,7 +318,7 @@ NEW request's body is irrelevant — the response reflects the ORIGINAL attempt' Outcomes: - **`201`** with the created (or, on a replay after success, the ORIGINAL) `ThreadView`. A - reply to a `closed` or `spam` conversation **reopens** it to `active` (v1.1, HT-26 — the + reply to a `closed` or `spam` conversation **reopens** it to `active` (v1.1, — the store's append policy) — only on the call that actually creates the row, not on a replay. - **`400 validation_failed`** on a missing/empty `Idempotency-Key` header, or a body that violates the limits. @@ -329,7 +329,7 @@ Outcomes: `201`. Replay-of-original-outcome does not survive a conversation delete — there is no mail-safety impact, since the original send already happened regardless of what a later replay call observes. -- **`409 retry_in_progress`** (HT-16) — the delivery lease for this `Idempotency-Key` is +- **`409 retry_in_progress`** — the delivery lease for this `Idempotency-Key` is currently held by another in-flight attempt; nothing was sent by this call. The caller should retry the SAME key later, not mint a new one (a new key would create an independent send, defeating the point of the dedup key). @@ -344,11 +344,11 @@ Outcomes: delivered, so a subsequent failure to record `'sent'` is NOT a `send_failed` — it resolves to `201`, since reporting a delivered message as failed would invite a resend.) -### 4b. `PATCH /api/v1/conversations/{id}` — set status (+ snooze, v1.1 HT-77) +### 4b. `PATCH /api/v1/conversations/{id}` — set status (+ snooze, v1.1) Body: `{ status: ConversationStatus; snoozedUntil?: string }` — `status` any of `active`, -`pending`, `closed`, `spam` (v1.1, HT-26); `snoozedUntil` an optional ISO-8601 timestamp -(v1.1, HT-77). Returns the updated `ConversationSummary` (`200`) and bumps `updatedAt` +`pending`, `closed`, `spam` (v1.1); `snoozedUntil` an optional ISO-8601 timestamp +(v1.1). Returns the updated `ConversationSummary` (`200`) and bumps `updatedAt` (a status change is activity — the conversation resurfaces in its folder). The store's `setConversationStatus(id, status, options)` **excludes `deleted`** (a deleted conversation is not reachable through this endpoint): missing or deleted → `404 not_found`; a body whose @@ -370,7 +370,7 @@ A snoozed conversation wakes itself two ways, both ending in `status: 'active'` reports through the SAME event a structurally-equivalent non-timed transition already uses, rather than both being forced through one shape. -- **Timer wake.** A periodic engine-internal pass flips it once `now() >= snoozedUntil`, no +- **Timer wake.** A periodic engine-internal pass flips it once `now >= snoozedUntil`, no Agent action, via `setConversationStatus` — the exact same write path (and therefore the exact same event) an Agent's own `PATCH` to `active` uses. Fires `conversation.status_changed` (`from: 'pending', to: 'active'`). @@ -384,12 +384,12 @@ uses, rather than both being forced through one shape. Scoped to genuinely inbound mail only: an Agent's own outbound reply or an internal note to a snoozed conversation never wakes it early. -### 4c. `POST /api/v1/conversations/{id}/notes` — internal note (v1.1, HT-28) +### 4c. `POST /api/v1/conversations/{id}/notes` — internal note (v1.1) An internal note is Agent-only context on a conversation. **It is never emailed and never touches the send path**: no reply token is minted, no outbox row is created, and the delivery worker never sees it — a `note` row existing anywhere near `sendReply` is a -bug, and HT-28 adds a test asserting the boundary (charter invariant #5 adjacency). +bug, and adds a test asserting the boundary under the charter's "Conversation integrity" rule. Body: `{ text: string }` — 1–5000 chars, server-enforced; no `html` (notes are plain text in v1). Outcomes: @@ -401,7 +401,7 @@ text in v1). Outcomes: - **`400 validation_failed`** on a body that violates the limits. - **`404 not_found`** if the conversation is missing or `deleted`. -### 4d. `DELETE /api/v1/conversations/{id}` — soft delete (v1.1, HT-30) +### 4d. `DELETE /api/v1/conversations/{id}` — soft delete (v1.1) Marks the conversation `deleted`. **`204`** with an empty body on success; `404 not_found` if the conversation is missing or already deleted. From that point the @@ -412,7 +412,7 @@ against it starts a fresh conversation (threading.md's existing deleted-conversa rule). The UI pairs this with a two-step arm (press → solid critical "Confirm" → auto-disarm) rather than a modal. -### 4e. `PUT /api/v1/conversations/{id}/tags` — replace the tag set (v1.1, HT-29) +### 4e. `PUT /api/v1/conversations/{id}/tags` — replace the tag set (v1.1) Body: `{ tags: string[] }` — **replace-set semantics**: the request's array becomes the conversation's whole tag set (send `[]` to clear). Each entry is trimmed, lowercased, @@ -423,7 +423,7 @@ entry, or an over-length entry is `400 validation_failed`. Returns the updated activity. Missing or deleted conversation → `404 not_found`. There is no tag-filtered listing in v1 — tags are display and organization until a real query need appears. -### 4f. `PUT /api/v1/conversations/{id}/assignee` — claim or release (v1.1, HT-31) +### 4f. `PUT /api/v1/conversations/{id}/assignee` — claim or release (v1.1) Body: `{ assignee: 'me' | null }` — `null` means "Anyone". Anything else is `400 validation_failed`. Returns the updated `ConversationSummary` (`200`). Does **not** @@ -433,9 +433,9 @@ This is deliberately NOT identity: `'me'` is the deployment's one operator (the token holder), stored as a flag, not a user id. It exists so the UI's "Mine" folder works in v1; the multi-Agent increment replaces `'me'` with real Agent ids and this endpoint's body shape is expected to change then (that is an acceptable v2 break — -dogfood-only, same reasoning as HT-16's). +dogfood-only, same reasoning as 's). -### 4g. Open tracking — `customerViewedAt` (v1.1, HT-32; config-gated, default OFF) +### 4g. Open tracking — `customerViewedAt` (v1.1,; config-gated, default OFF) Open tracking records the first time a customer's mail client fetched a tracking pixel embedded in an outbound reply, surfacing it as `customerViewedAt` on that outbound @@ -445,10 +445,10 @@ embedded in an outbound reply, surfacing it as `customerViewedAt` on that outbou are telemetry on customers, which sits uneasily with the ownership-and-trust positioning this project exists for. The operator must explicitly enable it in deployment configuration (an `InboxApiDeps`-level flag plus the deployment's public base URL, pinned -by HT-32). While disabled — the shipped default — no pixel is injected, the field is -always `null`, and outbound mail is **byte-identical** to pre-v1.1 behavior: HT-32 must +by). While disabled — the shipped default — no pixel is injected, the field is +always `null`, and outbound mail is **byte-identical** to pre-v1.1 behavior: must prove text bodies, headers, and threading unchanged against the existing fixtures -(charter invariant #5), and that enabling it alters only the HTML body. +(charter's "Conversation integrity" rule), and that enabling it alters only the HTML body. When enabled: @@ -458,7 +458,7 @@ When enabled: keyring/HMAC pattern reply tokens already use (`src/mail/reply-token.ts`), NEVER the bare thread uuid: a guessable identifier would let anyone who learns (or enumerates) an id forge a "customer viewed" signal. The exact route and token format are pinned by - HT-32 against that requirement. + against that requirement. - The pixel endpoint is the API's one **unauthenticated** surface, fetched by customer mail clients. Its contract: always respond `200` with `Content-Type: image/gif`, a fixed 1×1 gif body, and `Cache-Control: no-store` (a cached pixel would suppress the @@ -471,10 +471,10 @@ When enabled: Both §4a write paths grow `InboxApiDeps` with what `sendReply` needs — `sender` (`EmailSender`), `keyring`, `mailDomain`, and `supportAddress` — injected at deploy time -alongside `store` and `apiToken`; HT-32 adds the open-tracking configuration described +alongside `store` and `apiToken`; adds the open-tracking configuration described above. -### 4h. Saved replies & macros (v1.1, HT-76) +### 4h. Saved replies & macros (v1.1) A saved reply is a per-mailbox, reusable message definition an Agent can post as a reply body; a "macro" is the same row carrying `actions` — a set of state changes the CLIENT @@ -493,7 +493,7 @@ interface SavedReply { bodyHtml: string | null actions: { // {} default — a plain saved reply has no macro side effects setStatus?: 'closed' | 'pending' - addTags?: string[] // each 1-40 chars, trimmed + lowercased, deduplicated + addTags?: string[] // each 1-40 chars, trimmed + lowercased, deduplicated assignToSelf?: boolean } sortOrder: number // 0 default — display order within the mailbox's list @@ -525,7 +525,7 @@ this path" shape as everywhere else in this API — never a cross-mailbox edit). success; `404 not_found` for an unknown or cross-mailbox `{replyId}` (same rule as PATCH). Role gate is deliberately admin-only for every write in v1 — a future increment may relax -authoring to any Agent, but that is not this ticket's call to make. `GET` is open to every +authoring to any Agent, but that is outside this specification. `GET` is open to every Agent so the picker works regardless of who authored the library. ## 5. Security notes @@ -534,7 +534,7 @@ Agent so the picker works regardless of who authored the library. `