Ship local distribution and publisher
## Original requirement (verbatim, epic body)
> Build new deployment type for Jaah: local. Do a web research and copy the model of OpenClaw, N8N and other similar products:
> - Open source in a public repo (for not private), mirrored some of the components from main repo
> - A package of node + CP that user runs on his machine, no multiple machines, single user, no advanced functionality, no networking, no nodes
> - Should be a very easy start - one line install and/or build from repo and/or docker compose
> - Limited number of accounts
>
> Analyze and propose options
Settled upstream (do not reopen): PRD (Task 10080) decisions D1–D16 + assumptions S1–S9; Architecture (Task 10081) decisions D17–D29 — the **1+3 hybrid**: one codebase with a build-time edition, an allowlist manifest + tree builder + per-PR mirror-build gate, public repo `jaah-ai/jaah-local` (fair-code licence adapted from n8n's Sustainable Use License, read-only mirror: issues open, PRs closed; images on GHCR `ghcr.io/jaah-ai/jaah-local-{api,web,node}`), published on every `master` promotion (D19) and fail-closed until the licence is approved (D24). Node placement (D18): native by default on Linux; macOS/Windows run the node container.
## Scope of this child
**C7a. Public distribution files + mirror publisher + staging repo (Arch §2.7, first half of the architecture's C7; the planner split C7 so each half is one reviewable PR).** Owns the distribution files, the publisher workflow, the staging repo/environment setup and the image-build gate step. A workflow can only be dispatched once it is on `develop`, so the first staging publish and the AC-1/AC-2/AC-3 staging oracles run in C7b, right after this child merges.
## Approach (from the settled architecture — realize it, do not redesign)
- **`infra/local-edition/` (mirrored to the public repo root; add `infra/local-edition/**` to the manifest `include` with the `layout` mapping):**
- `compose.yml`: `postgres:18` (plain), `api`, `web` (nginx serve stage per `Dockerfile.frontend`), `node` under profile `node`; images `ghcr.io/jaah-ai/jaah-local-{api,web,node}:${JAAH_VERSION}`, each with a `build:` block (AC-6); every publish on `127.0.0.1:<port>` — the web port, and the API on exactly the port named in C3's `app/local_node.py` (the native node's CP URL); volumes `jaah-secrets`, `jaah-node-identity`, `jaah-node-home`, DB and attachments; no MinIO.
- `install.sh` (Linux/macOS) and `install.ps1` (Windows → Docker Desktop), one line from the README: check Docker → fetch `compose.yml` for the latest tag → `docker compose up -d` → on Linux run C5's `jaah-local-node install` (WSL counts as Windows: `WSL_DISTRO_NAME` set or `/proc/sys/fs/binfmt_misc/WSLInterop` exists → container node profile) → print `http://localhost:<port>`. No config edit. `--upgrade`: pull the new tag, `up -d`, and `jaah-local-node install --upgrade <tag>` when the native unit exists.
- `README.md` (quick start ≤ 25 lines, no node/org/tailnet words, every command copyable, plus a build-from-source section), `LICENSE` (adapted n8n Sustainable Use License with the cap clause and the anti-hosting clause — DRAFT pending D24), `RELEASE-CHECKLIST.md` (manual macOS/Windows/codex/antigravity/pi oracles, the no-auth public one-line run, and every CLI host with no telemetry off-switch from C5's PR).
- Public-repo CI under `infra/local-edition/.github/`: on tag `v*` build the three images from the repo and push to GHCR with `GITHUB_TOKEN`; a workflow auto-closing fork PRs with a pointer to Issues; actions pinned by SHA.
- **Publisher** `.github/workflows/local-edition-mirror.yml` (modelled on `agent-sdk-mirror.yml`):
- Triggers: `push` to `master` (D19) and `workflow_dispatch`.
- Destination: read from the GitHub environment variable `LOCAL_EDITION_DEST` of the environment the job runs in; a `workflow_dispatch` input may select the environment. No hard-coded default destination anywhere.
- Two environments: `local-edition-mirror` (master-only; holds the public deploy key; absent until the operator approves `LICENSE` — the job **fails closed** without it) and `local-edition-staging` (restricted to `develop`; `LOCAL_EDITION_DEST=jaah-ai/jaah-local-staging` and the staging deploy key). The master push uses `local-edition-mirror` only.
- Steps: `build_tree` → `check_tree` → one snapshot commit onto the destination `main` with the sync identity → tag `v<VERSION>` (`tools/version.sh`).
- **Gate:** add the three local image `docker build`s (from the built tree) to `local-edition-check.yml`.
- **Staging:** create the private repos `jaah-ai/jaah-local-staging` (same layout; issues on) and `jaah-ai/jaah-local-e2e-fixture` (for C7b), the `local-edition-staging` environment, and a staging deploy key — never echo a key value. Do not publish yet: the publisher can be dispatched only once it is on `develop` (C7b's first step).
Full documents: the epic's settled `prd` and `architecture` artifacts (Tasks 10080/10081).
Credentials & reach:
- AC-1/2 mechanics on the private staging repo; fixture repo creation — `me/users/roman@gmind.tech/github_admin` (jaah-ai org admin) — **verified** (`credential_get` ok)
- AC-1/2 on the public `jaah-ai/jaah-local` — operator action: approve the licence, create the repo and deploy key — **deferred**: the D24 licence gate. Mechanics are proven on staging first.
- AC-3, AC-17 grep, AC-8 migrations, C1–C6 unit and gate checks — nothing beyond the worktree (plus GitHub Actions for `local-edition-check.yml`) — **verified**: the workflow runs in this repo's existing CI
Out of scope:
- Do NOT create the public `jaah-ai/jaah-local` repo or its deploy key, and do NOT publish anything public — operator action after licence approval (D24). The master-push path must fail closed while the `local-edition-mirror` secret is absent.
- Do NOT run the fresh-VM release oracles (C7b). Do NOT rework C1–C6 internals except to fix a defect the image build exposes (fix in place, name it in the PR body).
- Do NOT add a new alembic revision. Do NOT add docs-portal pages (deferred). Never write a credential value into a workflow, file, log or PR body.
PRD §7 (verbatim) — the local edition, and the work to build it, MUST NOT:
- Change the behaviour, UI or data model of the cloud deployments (dev, `dev.jaah.ai`; prod, `app.jaah.ai`). Any edition switch must default to today's behaviour there.
- Ship, in the local edition or in the public repo: Stripe or billing; SES or system mail; Slack or Telegram; headscale or tailnet; cloud node launch (`apps/api/app/cloud_node_launch.py`); node groups (`apps/api/app/node_group_reconcile.py`); Multi-Node Hosts (`apps/api/app/vnodes.py`); NodeLink (`apps/api/app/nodelinks.py`); MicroVM (`docs/policies/microvm-nodes.md`); Jarvis (`apps/site-widget/`); the support knowledge base (`apps/api/app/support_kb/`); project memory (`apps/api/app/project_memory/`); SSO, SCIM or multi-org tenancy; the mobile app (`apps/mobile/`); GitHub Actions self-hosted runners (`infra/host/gha-runners/`); `infra/jaah/` (Terraform) and `infra/host/`.
- Require pgvector, Redis, S3/MinIO, or any cloud account.
- Phone home: no telemetry, no version check, no licence or activation server.
- Accept community PRs into the public repo, or require a CLA.
- Offer data export from local to Jaah Cloud, or attaching the local node to a cloud org.
- Rewrite the control plane for SQLite, or install the control plane natively without Docker.
- Expose any port beyond loopback, or support more than one machine.
- Make the caps (3 accounts, 10 concurrent sessions) configurable by any setting, env var, flag or UI.
Done means:
- Publisher tests: `actionlint` clean on both workflows; a guard step/test proves the job fails closed when the environment's deploy key or `LOCAL_EDITION_DEST` is absent, the destination comes only from `LOCAL_EDITION_DEST`, and the `master` push binds only `local-edition-mirror`.
- `build_tree` output contains `LICENSE`, `README.md`, `compose.yml`, `install.sh`, `install.ps1`, `RELEASE-CHECKLIST.md` and `.github/` at the root (layout mapping) and `check_tree` is green on it.
- `LICENSE` names both the limits clause (3 accounts / 10 concurrent agents) and the anti-hosting clause (AC-1: "The licence must explicitly forbid two things: (i) removing or circumventing the account and concurrency limits, and (ii) offering the software as a hosted or multi-tenant service. Fail if the licence is OSI-permissive, or if it does not name the limits."); README quick start ≤ 25 lines with no node/org/tailnet words (AC-7).
- Staging repo, fixture repo and the `local-edition-staging` environment (develop-only) exist — cite `jaah-forge api get` output, no key values.
- `local-edition-check.yml` image-build step green on this PR; `./dc check quick` green.
- **AC-1, AC-2, AC-3 staging oracles: run in C7b** (they need this child merged first).
Outcome
Local-edition distribution (compose, installers, Dockerfiles, draft LICENSE, README, checklist, public CI) and a fail-closed mirror publisher. Staging repos/env set up; image gate green; PR #6948 merged 76c7b3cc, Jaah Deploy succeeded. Drafts 10156, 10157. Memory: proposed 1 gotcha.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)