Run Linux local release verification
## 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** (build-time edition, allowlist mirror, caps in CP + node, public repo `jaah-ai/jaah-local`, fail-closed until licence approval D24). Binding numbers: 1 user, ≤3 AI provider accounts in total (any state/provider, S4), ≤10 concurrent agent sessions (over-cap work queues, never fails, S5), no pgvector, no telemetry, loopback only, Docker required.
Definitions (PRD §6): "Fresh machine" = a clean VM of the named OS with only Docker and git installed. "Each launch OS" = Ubuntu 24.04 x86_64, macOS 15 Apple Silicon, Windows 11 + WSL2 + Docker Desktop (macOS/Windows oracles deferred to the operator's `RELEASE-CHECKLIST.md`, D26). "Local install" = the stack installed by AC-4. Availability: "this edition does not run on the dev control plane. Its real user flow is a fresh-machine install, so every oracle below is run there, against a build cut from the release candidate. That build must come from the public repo, not from the monorepo." (staging stands in for the public repo until D24.)
## Scope of this child
**C7b. Linux release run (Arch §2.7 / §3 C7, second half).** Proves C1–C7a end to end on a fresh EC2 Ubuntu 24.04 VM installed from the private staging repo. Owns the first staging publish, AC-1, AC-2, AC-3 (staging), AC-4–AC-7, AC-11, AC-13, AC-17 (code-patch oracle + docs no-knob grep), AC-18, AC-19, AC-20, AC-22 (capture), and the real-install re-run of AC-8, AC-9, AC-10 (Linux claude/deepseek), AC-12, AC-14, AC-15, AC-16, AC-21, AC-23. Code changes only to fix a defect the run exposes.
## Approach (from the settled architecture — realize it, do not redesign)
- **Step 0 (needs C7a merged):** dispatch `.github/workflows/local-edition-mirror.yml` from `develop` with the `local-edition-staging` environment → snapshot commit + tag on `jaah-ai/jaah-local-staging`; staging CI builds and pushes the private GHCR images. This is the publisher's first real run; fix any defect in place.
- First step on the VM: `docker login ghcr.io` + a test pull of a staging image (if the package-read scope is missing, ASK for a package-read token — header `Credential`; never put a token value in a shell command line or log).
- Launch a fresh EC2 Ubuntu 24.04 (x86_64) with only Docker + git, plus a second EC2 in the same VPC for AC-13. Run every oracle below against the staging repo (registry/git login is the only extra allowed — staging mechanics only). Terminate both instances afterwards.
- Upgrade (AC-11): no previous tag exists, so install a build of the commit before, seed data, then upgrade to the release candidate.
- AC-17 code-patch: on a build from the staging source, patch only `apps/api` (route check, migration trigger, `edition.py`), rebuild, repeat AC-15/16; then only `apps/jaahd`.
- Fix any defect the run exposes in place (small PRs, named in the PR body); file a follow-up only for a large one.
- Cite every oracle's command and output in the PR body (or the settle report if no code change was needed).
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-4/5 and AC-1's logged-out read with no auth, on the public repo — none (anonymous GitHub and GHCR) — **deferred** to the release checklist after the first public publish (D24). Staging requires auth.
- Staging GHCR private-image pull from the EC2 VM — `me/users/roman@gmind.tech/github_admin`, which needs package-read scope — **deferred** to C7's first step: `docker login ghcr.io` plus a test pull. If the scope is missing, C7 asks the operator for a package-read token. Not probed here, so the token value never enters a shell command.
- AC-9/18/19: coder task, PR on `jaah-ai/jaah-local-e2e-fixture` — `me/GitHub (ruzhukov)` — **verified** (`credential_get` ok)
- AC-4–7, 11, 13, 22 on Ubuntu 24.04: fresh VM, plus a second EC2 in the same VPC for AC-13's LAN probe — `org/aws` (EC2 in the org account) — **verified** (`credential_get` ok)
- AC-10 claude — `me/jaah/system/anthropic_api_key` — **verified** (`credential_get` ok)
- AC-10 deepseek — `org/deepseek` — **verified** (`credential_get` ok)
- AC-4/5/10/11 on macOS 15 and Windows 11 — none on the roster — **deferred** to the operator's release checklist (D26)
- AC-10 codex, antigravity, pi — none on the roster — **deferred** to the operator's release checklist (D27)
Out of scope:
- Do NOT create or publish to the public `jaah-ai/jaah-local` repo (D24). Do NOT run the macOS/Windows/codex/antigravity/pi oracles (deferred — D26/D27, `RELEASE-CHECKLIST.md`).
- Do NOT redesign C1–C7a; fix only defects the run exposes. Do NOT add a new alembic revision unless a defect fix strictly needs one (re-check heads at final push).
- Never write a credential value anywhere; terminate every EC2 instance you start.
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: the `workflow_dispatch` run from `develop` into `local-edition-staging` succeeded (cite the run URL) and the staging tag build pushed the three images.
- **AC-1 (R1).** "A public repo exists, carries a fair-code licence, and its builds come only from mirrored code… the root `LICENSE` file is a source-available fair-code licence. 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." — verify on staging: `LICENSE` names both clauses and the limits; the images came only from the staging (mirrored) tree. Public logged-out read: (deferred — D24 licence gate; release checklist).
- **AC-2 (R2).** "The mirror is one-way and read-only, issues are open, and pull requests are closed… observe that issues are enabled. Observe that a PR from a fork is auto-closed or labelled with a message pointing at Issues… `git log --format='%an' | sort -u` shows only that identity." — verify on staging. (Public: deferred — D24.)
- **AC-3 (R3).** "run a secret scanner (e.g. gitleaks) over the full history of the public repo and observe zero findings" — on the staging repo's full history; plus `check_tree` re-scoped checks (D25) green on the published tree.
- **AC-4 (R10).** "On a fresh machine of each launch OS: 1. Paste the single documented command from the public README: one line, no prerequisites beyond Docker and git. 2. Observe the console at `http://localhost:<documented port>` within 10 minutes on a 50 Mbps connection. 3. Sign in as the single user. 4. Observe the Tasks page. Fail on any manual edit of a config file, or on any second command, other than the native-node step if the chosen sub-option makes it explicit and documented." — verify on the Ubuntu VM against staging; the native node comes up and shows online in "This machine". (No-auth public run: deferred — D24. macOS/Windows: deferred — D26.)
- **AC-5 (R11).** "on a fresh machine of each launch OS, run `git clone <public repo>` then `docker compose up -d` (or the single documented compose command). Observe the same result as AC-4." — verify on the Ubuntu VM against staging. (macOS/Windows: deferred — D26.)
- **AC-6 (R11).** "on a fresh Linux machine, follow the README's build-from-source section verbatim. Observe that images build from the public source and that the result passes AC-4 step 3–4. Fail if the build pulls any artefact that only exists in the private monorepo."
- **AC-7 (R10), strengthened.** "open the public README. Observe that the quick start fits one screen (≤ 25 lines), names the prerequisites, and that each command is copyable… install and first sign-in need no knowledge of nodes, orgs or tailnets. Observe that none of those words appear in the quick start." Tightened: each quick-start command, pasted verbatim in order on the fresh VM, works with no edits, and the text states the URL to open and the sign-in step in plain sentences.
- **AC-8 (R12).** "run `docker compose exec <db service> psql -U <user> -c "select extname from pg_extension"` and observe no `vector` row… the DB image is a plain Postgres image… all migrations run green."
- **AC-9 (R13).** "follow the documented native-node path. Observe the node shown as online in the console… run a coder task on a GitHub repo. Observe a fresh clone created under the node's working area and a PR opened." (fixture repo)
- **AC-10 (R13, S6).** "open Accounts, add one account of each provider Jaah supports… and run a one-line task on each. Observe a completed session for every provider." — Linux: claude, deepseek. (codex, antigravity, pi and macOS/Windows: deferred — D26/D27.)
- **AC-11 (R14).** "create a project, an account and two tasks. Run the documented upgrade command to the next build. Observe the same project, account and tasks, and that the stack is healthy." — from the previous-commit build, native node included.
- **AC-12 (R4).** "there is no sign-up page, no invite UI, and no Users management page… `POST` to the user-create and invite endpoints returns 403 or 404 with a message that names Jaah Cloud. States: before first sign-in… and after." — on the install.
- **AC-13 (R5).** "run `ss -ltnp` on the host and observe that every listener the install publishes is on `127.0.0.1` or `::1`… from a second machine on the LAN, `curl http://<host-ip>:<port>` fails… no tailnet or headscale process or container exists."
- **AC-14 (R6).** "the console has no Nodes list, no 'Add node', no node groups, no Multi-Node Hosts, and no NodeLink screens… the node-create, node-group and NodeLink API routes return 404… exactly one node exists, the local one, created by the install." — on the install.
- **AC-15 (R7).** "Accounts 1–3 succeed in any mix of providers and auth kinds. The 4th is refused with copy that states the limit (3) and links to Jaah Cloud. States: (i) 3 enabled accounts → the 4th is refused; (ii) 2 enabled and 1 disabled → the 4th is still refused (S4); (iii) delete one of 3 → a new one succeeds; (iv) the API path (`POST` directly) is refused the same way as the UI path."
- **AC-16 (R8).** "queue 12 independent tasks (and, separately, an epic whose plan fans out to more than 10 coders). Observe at most 10 running sessions at any moment, both on the console's session view and as agent processes on the node… the rest show as queued, then start as slots free… no task fails because of the cap. States: idle; exactly 10 running; 10 running plus queued; a session ending frees a slot."
- **AC-17 (R9).** "Each cap is enforced in at least two independent layers, the control plane and the node… raise the cap only in the control-plane layer, rebuild, and repeat AC-15 and AC-16. Observe that the other layer still refuses the 4th account (or the 11th session)… the same with only the node layer patched… the caps are not settable by an env var, a settings page, a DB row the UI can edit, or a CLI flag. Grep the public repo docs and observe no knob." — CP layer = everything under `apps/api` (route check, migration trigger, `edition.py`); node layer = everything under `apps/jaahd`; docs grep over the full staging tree.
- **AC-18 (R15).** "create an epic, press Start, and observe the business-analysis, architecture, plan and coder stages run in order to a merged PR on a GitHub test repo." (fixture repo)
- **AC-19 (R16).** "connect GitHub (by OAuth or pasted token), add a repo as a project, run a coder task, and observe a branch pushed and a PR opened under the user's identity." (token paste, D22)
- **AC-20 (R17).** "create a schedule that fires in 2 minutes and observe the task start… create a When→Target→Action automation and observe it fire… open the advisor chat, ask about a task, and observe a reply."
- **AC-21 (R18).** "walk every top-level navigation entry and every Settings tab of the local console. Observe none of the following: billing, plans or Stripe; orgs or tenant switching; users and invites; SSO or SCIM; nodes, node groups, Multi-Node Hosts, NodeLink, MicroVM; Jarvis (site widget, Support → Website chats); the Help assistant knowledge base; project memory; Slack and Telegram; headscale or tailnet; Audit SIEM export; data residency… the matching API routes return 404. Quality: no empty tab, dead link, or 'coming soon' placeholder is left where a removed surface was." — on the real install (built tree without `routers_cloud.py`/`routesCloud.tsx`), with screenshots at desktop and phone width.
- **AC-22 (R19).** "run the local install for 30 minutes with a task running while capturing DNS and outbound connections on the host… Observe outbound traffic only to the AI providers, GitHub, and the container and package registries used at install. Observe zero connections to any Jaah-owned domain." (D29: CLI hosts listed in `RELEASE-CHECKLIST.md` allowed; zero Jaah-owned domains stays absolute.)
- **AC-23 (R20).** "hitting the account cap (AC-15), hitting the concurrency cap while tasks queue (AC-16), and the places of removed cloud features each show one short line with a working link to Jaah Cloud sign-up… no modal blocks work, and no upsell appears on pages where no limit or removed feature is involved." — triggered by the real caps on the install.
- Every oracle's command + output cited; both EC2 instances terminated.
Outcome
Staging publish ran; Ubuntu 24.04 VM run found 9 defects, fixed in PR #6956 (merged bc5e57b1, review lanes pass). Not release-ready: tree can't build api/node (draft 10167), native runner (10145), UI leftovers (10170), session store (10171). Deploy failed on dirty GHA runner (10176). EC2 terminated. Memory: none applied; proposed 1 gotcha.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)