From a7f3dcc39df1ea56e17d3eab601a09ffcba8a562 Mon Sep 17 00:00:00 2001 From: Morrow Contributors Date: Fri, 14 Aug 2026 15:42:55 +0800 Subject: [PATCH] Make the repository adoptable by anyone deploying Able MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Able's tree was already tenant-neutral, but nothing told a stranger how to run it, and nothing told an agent which of the two jobs in this repository it was doing. An adopter's agent reading AGENTS.md found only upstream maintenance rules, which are actively misleading when the whole task is to configure one specific business. Add docs/adopt.md as the adopter entry point: prerequisites, fork-with- upstream rather than a template snapshot, the tenant boundary as an explicit table, the three customization levels, the deployment steps only a human can complete, and the upgrade path. Declare a private context slot. AGENTS.local.md is git-ignored and holds a deployment's standing context; AGENTS.local.example.md is the committed template. AGENTS.md now branches upstream work from deployment work and reads that file first when it exists. The public-readiness scan rejects any published *.local.md and requires both new documents, so the boundary is enforced rather than merely described. Narrow the blanket .claude/ ignore to local session state so committed agent assets ship to adopters, and add the deploy-able skill, which drives docs/deployment.md in order and stops at every human gate — including the decision to enable public intake. Co-Authored-By: Claude Opus 5 --- .claude/skills/deploy-able/SKILL.md | 46 +++++++++++++++ .gitignore | 12 +++- AGENTS.local.example.md | 41 +++++++++++++ AGENTS.md | 11 +++- CHANGELOG.md | 8 +++ README.md | 7 ++- docs/adopt.md | 91 +++++++++++++++++++++++++++++ scripts/scan-public-readiness.mjs | 8 ++- 8 files changed, 217 insertions(+), 7 deletions(-) create mode 100644 .claude/skills/deploy-able/SKILL.md create mode 100644 AGENTS.local.example.md create mode 100644 docs/adopt.md diff --git a/.claude/skills/deploy-able/SKILL.md b/.claude/skills/deploy-able/SKILL.md new file mode 100644 index 0000000..7a86111 --- /dev/null +++ b/.claude/skills/deploy-able/SKILL.md @@ -0,0 +1,46 @@ +--- +name: deploy-able +description: Drive an Able Desk deployment onto the user's own Cloudflare account, in order, stopping at every step only a human can complete. Use when the user wants to deploy Able, set it up for their business, stand up a support desk, resume a half-finished deployment, or check what is left before real customers can be served. +--- + +# Deploy Able for a business + +You are standing up **one single-tenant Able Desk deployment** in the user's Cloudflare account, on their domains, holding their data. There is no hosted Able service to sign into. + +[docs/deployment.md](../../../docs/deployment.md) is the authoritative runbook — read it before acting, and follow its steps in order. This skill is the driver: it tells you how to sequence the work, what you may do unattended, and where to stop. + +## Before you start + +1. Read `AGENTS.local.md` if it exists. It is the deployment's standing context — hostnames, which rails are on, where the deployment left off. If it does not exist, copy `AGENTS.local.example.md` to `AGENTS.local.md` and fill it in with the user as you go. Never commit it. +2. Read `docs/adopt.md` for the tenant boundary. Nothing about this business goes into a tracked file. +3. Establish where the deployment already is. A resumed deployment is the normal case — check what exists before creating anything: `npx wrangler whoami`, `npx wrangler d1 list`, `npx wrangler r2 bucket list`, `npx wrangler secret list`, and whether the Worker already serves `/healthz`. + +## Sequence + +Work `docs/deployment.md` §1 → §8 in order. Do not skip ahead: later steps fail confusingly when an earlier gate is incomplete, and several steps are irreversible in practice once customers are pointed at them. + +Announce which step you are on, what you are about to run, and what the user must do themselves. After each step, verify rather than assume — the runbook's §7 list is the acceptance criteria, not a formality. + +## You may do these unattended + +- `npm ci`, `npm run check`, `npm run build:assets`. +- Read-only Wrangler and Cloudflare inspection to establish current state. +- Apply committed D1 migrations (`npm run db:migrate:local`; `npm run db:migrate:remote` once the user confirms the target). +- Generate values that should be random — for example a `CUSTOMER_CAPABILITY_SECRET` of at least 32 characters — and hand them to the user to set. Do not put a generated secret into a file, a commit, or your visible output beyond what the user needs to paste. +- Draft workspace settings, knowledge articles, and email templates for the user's review. +- Run the §7 validation checks and report results truthfully, including failures. + +## Stop and hand to the user + +These are not automatable, and attempting them produces a half-configured deployment that looks finished: + +- Proving domain ownership and binding the two custom hostnames. +- Creating the Cloudflare Access application, its policy, and its group membership; enabling Managed OAuth for MCP clients. +- Setting secrets. Ask the user to run each `npx wrangler secret put …` themselves. Never accept a secret value into the conversation, a file, or a command line. +- Email Service onboarding, sender authentication, and confirming a real message landed in a real inbox. Provider acceptance is not delivery. +- Creating the Turnstile widget, the Meta app and WABA, or any Shopify client. +- **Enabling public intake and pointing real customers at the deployment.** This is the user's decision, made after §7 passes — never yours. + +## Report honestly + +A provisioned Worker is not an operational support desk. When you finish a session, state plainly which steps are complete, which are gated on the user, and which §7 checks have actually been run versus assumed. If a check failed, say so with its output. Update `AGENTS.local.md` with the new state so the next session resumes instead of restarting. diff --git a/.gitignore b/.gitignore index 8efdc52..1fa3911 100644 --- a/.gitignore +++ b/.gitignore @@ -18,5 +18,13 @@ raw-imports/ attachments/ worker-configuration.d.ts wrangler.production.generated.json -.claude/ -!.github/ + +# Agent working state. Committed agent assets (.claude/skills, .claude/agents) +# ship to adopters; local session state does not. +.claude/worktrees/ +.claude/settings.local.json + +# Private deployment context. AGENTS.local.md is where a deployer's own +# business may be named; the committed example template is not ignored. +AGENTS.local.md +*.local.md diff --git a/AGENTS.local.example.md b/AGENTS.local.example.md new file mode 100644 index 0000000..1e3d939 --- /dev/null +++ b/AGENTS.local.example.md @@ -0,0 +1,41 @@ +# Private deployment context (example) + +Copy this file to `AGENTS.local.md` and fill it in. `AGENTS.local.md` is git-ignored and rejected by `npm run scan:public`; it is the one place in your working copy where your business may be named. + +It is **context, not credentials.** Record what your agent needs to reason about your deployment. Never put a secret, token, capability, customer record, or raw export here — those belong in Wrangler secrets, Workers Builds variables, and `.dev.vars`. + +Delete the guidance lines and keep it short. A page an agent reads at the start of every session beats a runbook nobody maintains. + +--- + +## Deployment + +- **Business:** +- **Public portal hostname:** +- **Operator hostname:** +- **Cloudflare account / Worker:** +- **Where resource coordinates live:** +- **Deployment trigger:** + +## Rails enabled + +- **Email:** +- **Public intake:** +- **WhatsApp:** +- **Shopify order lookup:** +- **Voice assistance:** + +## Where we are + +- **Current goal:** +- **Cutover state:** +- **Known gaps:** +- **Open decisions:** + +## House rules for agents in this working copy + +- Tenant facts stay here, in the deployment platform, or in the running workspace settings — never in tracked files, tests, fixtures, issues, or commit messages. +- Prefer level 1 (workspace settings) over level 2 (code) for anything customer-visible. See [docs/adopt.md](docs/adopt.md). +- When a problem here is generic, fix it upstream with a neutral test rather than patching locally. +- Run `npm run check` before any deploy; it includes the public-readiness scan. +- diff --git a/AGENTS.md b/AGENTS.md index bc3d323..d4bba65 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,15 @@ Build a trustworthy, agent-first ERP for service businesses. Able Desk is the first application: a single-tenant Desk + CRM foundation on Cloudflare Workers where MCP is the normal operator interface, customers use accessible approved channels, and `/ops` is a small Cloudflare Access-protected recovery surface. Grow through deep business modules with small agent interfaces—not generic CRUD, a plug-in framework, or a universal object model. +## Working context + +Two different jobs happen in this repository. Decide which one you are doing before you change anything. + +- **Upstream work** — improving Able itself. Everything you produce must be tenant-neutral: generic issues, neutral fixtures, no business named anywhere. This is the default when no private context is present. +- **Deployment work** — configuring, deploying, or extending Able for one specific business. If `AGENTS.local.md` exists, read it first: it holds that deployment's standing context. Tenant facts belong there, in the deployment platform, or in runtime workspace settings — never in tracked files, tests, fixtures, commit messages, or issues. + +Prefer the lowest customization level that solves the problem: runtime workspace settings, then a module or adapter that respects the public seams, then core change. [docs/adopt.md](docs/adopt.md) defines the boundary and the levels; when deployment work uncovers a generic defect, fix it upstream with a neutral test instead of patching locally. + ## Operating model Act as the lead orchestrator. Start from the user outcome, inspect relevant evidence, define the completion bar, and own integration and final verification. For sizeable work with genuinely independent streams, delegate bounded research, implementation, or review tasks with the context, constraints, success criteria, and expected return each delegate needs. Run safe independent reads in parallel. Work directly when the task is simple, sequential, or tightly coupled; do not delegate merely to duplicate work. @@ -22,4 +31,4 @@ For an in-scope local change, implement it and run relevant non-destructive vali ## Canonical references -Use [README.md](README.md) for the project and repository map, [docs/product-v1.md](docs/product-v1.md) for the binding Desk V1 contract, [docs/architecture.md](docs/architecture.md) and [docs/adr/](docs/adr/) for design boundaries, [docs/development.md](docs/development.md) for the upstream/proving-ground workflow, and [docs/roadmap.md](docs/roadmap.md) for direction. Put detailed path-specific rules beside the code or documentation they govern. +Use [README.md](README.md) for the project and repository map, [docs/adopt.md](docs/adopt.md) for the adopter journey and tenant boundary, [docs/product-v1.md](docs/product-v1.md) for the binding Desk V1 contract, [docs/architecture.md](docs/architecture.md) and [docs/adr/](docs/adr/) for design boundaries, [docs/development.md](docs/development.md) for the upstream/proving-ground workflow, and [docs/roadmap.md](docs/roadmap.md) for direction. Put detailed path-specific rules beside the code or documentation they govern. diff --git a/CHANGELOG.md b/CHANGELOG.md index 235e210..2689f31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ All notable changes to Able are documented in this file. The format follows [Kee ### Added - A public catalog-only UCP agent profile and Shopify Storefront Catalog MCP integration for bounded, live product search and detail. +- Adopter guide at `docs/adopt.md`: prerequisites, fork-and-track-upstream setup, the tenant boundary, three customization levels, the human-only deployment gates, and the upgrade path. +- `AGENTS.local.example.md`, a template for the git-ignored `AGENTS.local.md` where a deployer's own standing context lives. `npm run scan:public` now rejects any published `*.local.md` and requires both new documents. +- Committed `/deploy-able` agent skill that drives `docs/deployment.md` in order and stops at every step only a human can complete. + +### Changed + +- `AGENTS.md` distinguishes upstream work from deployment work, points agents at `AGENTS.local.md` for private context, and states the customization-level preference. +- `.gitignore` narrows the blanket `.claude/` rule to local session state so committed agent assets ship to adopters. ## [0.1.0] - 2026-08-01 diff --git a/README.md b/README.md index bcbb48c..5a010a3 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ Able Desk is the first application. It combines a support desk, customer directo > **Project status:** early V1. The architecture and core Desk flows are implemented and tested, but Able does not yet claim production readiness. A deployed Worker is not, by itself, an operational support desk. -[Project site](https://codeyogi911.github.io/able-site/) · [V1 contract](docs/product-v1.md) · [Roadmap](docs/roadmap.md) · [Architecture overview](docs/architecture-overview.md) · [Architecture reference](docs/architecture.md) +**Running it for your business?** Start with [docs/adopt.md](docs/adopt.md) — Able deploys into your own Cloudflare account, on your domains, holding your data. There is no hosted service. + +[Project site](https://codeyogi911.github.io/able-site/) · [Adopt Able](docs/adopt.md) · [V1 contract](docs/product-v1.md) · [Roadmap](docs/roadmap.md) · [Architecture overview](docs/architecture-overview.md) · [Architecture reference](docs/architecture.md) ![Able Desk architecture overview](docs/assets/architecture-overview.svg) @@ -43,8 +45,9 @@ able/ ├── apps/ │ └── desk/ # Buildable Desk Worker, migrations, tests, assets, and configs ├── packages/ # Future reusable modules, extracted only behind proven interfaces -├── docs/ # Product contract, architecture visuals, ADRs, deployment, and roadmap +├── docs/ # Adopter guide, product contract, architecture, ADRs, deployment, roadmap ├── scripts/ # Repository-wide publication and history gates +├── .claude/skills/ # Committed agent skills, including the guided deployment driver └── .github/ # CI, security scanning, and contributor templates ``` diff --git a/docs/adopt.md b/docs/adopt.md new file mode 100644 index 0000000..d505403 --- /dev/null +++ b/docs/adopt.md @@ -0,0 +1,91 @@ +# Adopt Able for your business + +This guide is for someone who wants to **run** Able, not develop it. It covers what to bring, how to take a copy that can still receive upstream fixes, where your business's identity is allowed to live, and how much of the work an AI agent can do for you. + +If you are here to change Able itself, read [CONTRIBUTING.md](../CONTRIBUTING.md) and [development.md](development.md) instead. + +## What Able is, honestly + +Able Desk is a single-tenant support desk and CRM that runs as one Cloudflare Worker in **your** Cloudflare account, on **your** domain, holding **your** data. There is no Able-hosted service, no sign-up, and no vendor between you and your customers. + +It is early V1. The architecture and Desk flows are implemented and tested, but a deployed Worker is not by itself an operational support desk — several steps in [deployment.md](deployment.md) are gates, not suggestions. Do not point real customers at it until you have worked through them. + +## What you need to bring + +| Requirement | Why | Notes | +| --- | --- | --- | +| A Cloudflare account, Workers Paid | Email Service outbound sending | 3,000 outbound messages/month included; inbound routing is unlimited. Confirm [current pricing](https://developers.cloudflare.com/email-service/platform/pricing/). | +| A domain on Cloudflare | Two hostnames on the same Worker | e.g. `help.example.com` (public portal) and `desk.example.com` (operators). | +| Cloudflare Zero Trust (Access) | Operator identity | One self-hosted Access application over the whole operator hostname. | +| A mail domain you control | Sending and receiving support mail | Sender authentication must pass before public intake can be enabled. | +| Node.js 22 or 24+, npm, git | Build and deploy | See `.nvmrc`. | +| An MCP-capable agent client | The normal operator interface | Able is operated through MCP tools first; `/ops` is recovery, not a second desk. | + +Optional rails, each independently switchable: WhatsApp Cloud API (Meta app + WABA), Shopify product discovery over the catalog-only UCP profile, Shopify read-only order lookup, Shopify customer-account sign-in, browser voice assistance. + +## Try it before you provision + +`npm install && npm run dev:parity` builds the same assets and Worker entry point used for deployment, applies every migration, and seeds only neutral `example.test` fixtures. No Cloudflare resources, customer data, or secrets are required. Read the topology caveats in [development.md](development.md) first — notably that the microphone is deliberately disabled locally, and that Access, Turnstile, and email acceptance are only real on a deployed hostname. + +## Fork — do not use a template copy + +Take a **fork** (or a clone with `upstream` wired up), not a detached snapshot. Able ships schema migrations and security fixes; a copy that cannot pull them becomes a liability within a release or two. + +```bash +git clone https://github.com/codeyogi911/able.git my-desk && cd my-desk && git remote add upstream https://github.com/codeyogi911/able.git +``` + +Then follow [deployment.md](deployment.md) end to end. + +## The tenant boundary + +The single rule that keeps your deployment upgradable: **your business never appears in the repository.** Everything specific to you lives in one of three places, none of which is source control. + +| Where it lives | What belongs there | Examples | +| --- | --- | --- | +| **Runtime workspace settings** (D1, edited via `/ops/settings` or the admin MCP tools) | Everything customer-visible | Display name, portal title, logo, favicon, colors, font, case prefix, locale, timezone, support hours, sender addresses, portal base URL, the four customer email templates, knowledge articles | +| **Deployment platform** (Wrangler secrets and Workers Builds variables) | Credentials and resource coordinates | Operator hostname, Access AUD and team domain, owner email, Turnstile keys, capability secret, WhatsApp and Shopify credentials, `ABLE_D1_DATABASE_ID`, `ABLE_R2_BUCKET_NAME`, `ABLE_MEDIA_QUEUE_NAME`, `ABLE_WORKER_NAME` | +| **Your private working context** (`AGENTS.local.md`, `.dev.vars` — both git-ignored) | Local context and local-only values | Who you are, which rails you run, your cutover state, house rules for your agents; local development values in `.dev.vars` | +| **The repository** | Nothing about you | — | + +`npm run scan:public` enforces the last row on every `npm run check`: it rejects personal email addresses, concrete resource identifiers, secret-bearing file types, private keys, and any file matching `*.local.md`. Private release automation can add your own brand and domain names through the `ABLE_PRIVATE_DENYLIST` environment variable without teaching the repository those strings. + +Because branding is runtime data, `git pull upstream main` never conflicts with your logo, your colors, or your email copy. + +## Three levels of customization + +Reach for the lowest level that solves the problem. Level 1 survives every upgrade untouched; level 3 is where merge pain lives. + +**Level 1 — Configure. No code, no deploy.** +Brand and portal identity, the four customer notification templates, knowledge articles, case prefix and locale, support hours, which optional rails are on. Done through `/ops/settings` and the admin MCP tools (`able_portal_customize`, `able_email_customize`, `able_article_put`), all audited and validated. Custom CSS, remote fonts, and scripts are deliberately not accepted here — that guardrail is what makes agent-authored branding changes safe. + +**Level 2 — Extend. Code in your fork, upstream-shaped.** +A new channel adapter, an extra MCP tool over existing module interfaces, a new deep module for your vertical (inventory, dispatch, billing). Follow [ADR 0001](adr/0001-agent-first-suite.md): business policy, lifecycle, audit, idempotency, and outbox behavior live inside the owning module; adapters only authenticate, translate transport, and render. A module that respects those seams rebases cleanly across upstream releases — and is worth contributing back. + +**Level 3 — Change core behavior.** Editing Helpdesk lifecycle rules, the capability model, the revision contract, or the migration set. You now own a fork with real merge cost. If the change is generic, open an issue upstream first — the fix is usually cheaper as an upstream change than as a permanent local patch. + +## Working with an agent + +Able is built to be operated and deployed by agents, but some steps are irreducibly human. Give your agent [deployment.md](deployment.md) and let it drive; hold the following yourself. + +An agent can: install and build, run `npm run check`, apply migrations, prepare and run deploys, generate configuration values, draft knowledge articles and email templates, and — once `/mcp` is live — run the whole Desk loop. + +Only a human can: prove domain ownership, set Access policy and group membership, complete Email Service onboarding and verify a real inbox, create Turnstile widgets and Meta or Shopify apps, hold secrets, and make the final decision to enable public intake and point customers at the deployment. + +If you use Claude Code, the committed `/deploy-able` skill walks the deployment in order and stops at each human gate. Record your deployment's standing context in `AGENTS.local.md` — copy [AGENTS.local.example.md](../AGENTS.local.example.md) to get started. That file is git-ignored by design: it is how your agent remembers your business between sessions without any of it reaching the repository. + +## Taking upstream updates + +```bash +git fetch upstream && git merge upstream/main +npm ci && npm run check +npm run deploy:production # applies pending additive migrations, then deploys +``` + +Committed D1 migrations are additive or carry an explicit compatibility plan, so a merge does not rewrite your history. Read [CHANGELOG.md](../CHANGELOG.md) before upgrading; until 1.0.0, minor versions may contain breaking changes and each is called out there. + +## Before you serve real customers + +Work the validation list in [deployment.md](deployment.md) §7, and confirm at minimum: Access denies an unapproved browser; portal routes 404 on the operator hostname and operator routes 404 on the public one; a test request delivers a usable private link to a real inbox; a private link cannot read another case's attachment; `/ops` reports outbox failures truthfully. Read [privacy-and-backups.md](privacy-and-backups.md) and make sure your privacy notice covers Workers AI, Images, and any channel provider you enabled. + +Questions belong in [Discussions](https://github.com/codeyogi911/able/discussions). When you report anything, strip your customers, hostnames, and identifiers — see [SUPPORT.md](../SUPPORT.md). diff --git a/scripts/scan-public-readiness.mjs b/scripts/scan-public-readiness.mjs index 16cb139..0a7550c 100644 --- a/scripts/scan-public-readiness.mjs +++ b/scripts/scan-public-readiness.mjs @@ -8,7 +8,11 @@ import { promisify } from 'node:util' const root = process.cwd() const ignoredDirectories = new Set(['.git', '.wrangler', 'node_modules', 'dist', 'coverage', 'playwright-report', 'test-results']) const ignoredFiles = new Set(['package-lock.json', 'worker-configuration.d.ts', 'wrangler.production.generated.json']) -const forbiddenNames = [/\.sqlite(?:3)?$/i, /\.db$/i, /\.zip$/i, /\.pem$/i, /\.p12$/i, /\.pfx$/i, /\.key$/i, /^\.npmrc$/, /^\.dev\.vars$/, /^\.env(?:\.|$)/] +// `*.local.md` is the declared slot for a deployer's private context +// (AGENTS.local.md). It is git-ignored, so this only fires when one is force +// added — which is exactly the mistake worth blocking. The committed +// AGENTS.local.example.md template does not match. +const forbiddenNames = [/\.sqlite(?:3)?$/i, /\.db$/i, /\.zip$/i, /\.pem$/i, /\.p12$/i, /\.pfx$/i, /\.key$/i, /\.local\.md$/i, /^\.npmrc$/, /^\.dev\.vars$/, /^\.env(?:\.|$)/] // Deployment-specific identities do not belong in this public repository, // even as an encoded denylist. Private release automation can inject one // token per line without teaching the source tree those values. @@ -16,7 +20,7 @@ const customerTokens = (process.env.ABLE_PRIVATE_DENYLIST ?? '') .split(/\r?\n/) .map((value) => value.trim()) .filter(Boolean) -const required = ['LICENSE', 'README.md', 'SECURITY.md', 'CONTRIBUTING.md', 'CODE_OF_CONDUCT.md', 'docs/architecture.md', 'docs/deployment.md', 'docs/privacy-and-backups.md'] +const required = ['LICENSE', 'README.md', 'SECURITY.md', 'CONTRIBUTING.md', 'CODE_OF_CONDUCT.md', 'AGENTS.local.example.md', 'docs/adopt.md', 'docs/architecture.md', 'docs/deployment.md', 'docs/privacy-and-backups.md'] const textExtensions = new Set(['', '.css', '.html', '.js', '.json', '.jsonc', '.md', '.mjs', '.sql', '.ts', '.tsx', '.txt', '.yaml', '.yml']) async function walk(directory) {