Template API, schema and task seam

Task 11070 · Running · Coder · created · updated

## Epic requirement (verbatim) > Build a system of task templates (blueprints): > - Purpose: allow users to launch curated task definitions (description, persona, etc.) for common scenarios > - Operator will manage the library (CRUD) - standard style: list, tabbed detail. Reuse Task Edit code > - A template will have an accompanying image to graphically explain what it does > - Users should be able to browse the templates and Start any of them, favorite > - In the Templates view/ browser: modern cards with images, Most popular, Favorite, grouping by purpose, full text search > > Use best practices, minimum prose. Web research similar implementations. The settled inputs are the PRD (Task 11059) and the architecture (Task 11060); AC-n, D-n and N-n refer to the PRD. Read the architecture §2, §3 and §6a in full. This child builds them as pinned contracts, and every later child builds against them. ## Scope (this child) It is the foundation every other child builds on. - **Schema, one additive migration (arch §2), covering ALL of the following:** - the `_TemplateColumns` mixin in the new `apps/api/app/models/task_templates.py`; - `task_templates` (Shared); - `org_task_templates` (OrgScoped, unique `(org_id, slug)`); - **`task_template_assets` (Shared)**; - **the `org_template_id` subject column on `task_attachments`** (subjects tuple at `models/task_extras.py:321`, a composite FK with ON DELETE CASCADE, shaped like `automation_id`); - `task_template_favorites`; - `task_template_start_days` and `org_task_template_start_days`; - `tasks.template_id`, `tasks.org_template_id` (composite FK `ON DELETE SET NULL (org_template_id)`) and `tasks.template_name`; - `billing_plans.org_templates_enabled`, `orgs.org_templates_enabled_override`, `SwitchSpec("org_templates_enabled", "Templates (org)")` and the `org_templates_enabled(session, org)` helper in `plan_terms.py`; - the `templates:edit` permission in `authz/catalog.py`, with `role_permissions` seeded for owner and admin; - the tsvector GENERATED column with a GIN index (pattern: `project_memory/models.py:58-92`). - **Deviation from arch §7, recorded:** the assets table and the `task_attachments` column live HERE, not in C1b. A concurrency wave may carry only one migration, and C2's seed is the phase-2 migration. - **Tenancy:** verdicts in `app/tenancy/manifest.py`. - **Routes:** `apps/api/app/routes/task_templates.py`, prefix `/api/templates`, carries every §3 route except the two `/assets` routes: - list, with FTS (sanitized tokens, `:*` on the last token, `ts_rank_cd`, UNION ALL over both tables, max 200); - manage, get, refs, usage, create, patch, delete, duplicate, favorite PUT/DELETE; - `TemplateCard` and `TemplateDetail` exactly as pinned, including `may_start` and `editable`; - `image_url` = `/templates/<slug>.svg` for `builtin:`, otherwise null until C1b. - **Task-create seam:** - `TaskCreate` (`routes/tasks/create.py:368`) gains `template_id`. The server resolves the template visible and published, or returns 422 `template_id: Template unavailable`. It sets the link and `template_name`, and bumps the counter only when the creator is a person (I-2). - `TaskOut` gains `template: {id|null, name}|null`. - **Asset-copy seam:** the new `apps/api/app/task_template_assets.py` exports `copy_template_assets(...)` and `copy_assets_between_templates(...)` with the exact §3 signatures and bodies that do nothing. Create and duplicate call them. - **Community edition (§6a):** nothing in the mirror's sensitive set and no cloud-only import. Run the `tools/community_edition` check. ## Acceptance criteria — API halves owned here (verbatim from the PRD) - **AC-3 Create.** Oracle: from `/templates/manage`, open New. It goes to `/templates/new`, the same page in create mode. Expected: save as draft; the template appears in the list and **not** in the gallery. Publish it; it appears in the gallery. - **AC-4 Delete.** Oracle: delete a template that has started tasks and one automation pointing at it. Expected: the confirm names how many automations will stop. Afterwards the gallery no longer shows it, the task still shows the template's name as text, and the automation shows "Template deleted" (AC-15). - **AC-8 Grouping and sort.** Expected: the gallery opens with sections **Favorites** (if any), **Most popular** (top 6 by D8), then one section per purpose group (§9 order), each with a count. A filter chip per group narrows to that group; "Clear" resets it. Oracle: start a template 3 times, reload, and see it move up in Most popular. - **AC-9 Start (one click).** Oracle: on a card, press Start, pick a project in the dialog, confirm. Expected: a task is created with the template's title, description, persona, depth, involvement and model. It is started (state `active` or, for an epic, its first stage queued), and the browser lands on the new task's detail. The task detail shows "From template: <name>". The template's 90-day start count goes up by 1. The dialog lists only projects the user can start tasks in. Every Start precondition (`routes/tasks/dispatch.py::start_task`) surfaces as its existing message. - **AC-10 Customize.** Oracle: press Customize. Expected: `/tasks/new` opens with every template field filled in and the project empty. Changing the description and pressing Create & Start makes a task with the edited text, linked to the template and counted once. - **AC-11 Favorite.** Oracle: toggle the star on a card, reload, and sign in as another user in the same org. Expected: the first user sees the card in Favorites; the second does not. - **AC-12 Search.** Oracle: type a word that appears only in one template's description, such as "flaky". Expected: within 1 s the grid shows only matching cards, best match first, without the section grouping. Clearing the box restores the sections. A query with no matches shows the empty state: "No templates match" and a Clear button. - **AC-13 Plan switch.** A new plan switch, "Templates (org)", is off on every plan by default, with a per-org override, the same shape as `playbooks_enabled` in `apps/api/app/plan_terms.py`. Oracle, switch off: as an org admin, the gallery shows the global templates but no New template, no Duplicate, and no manage view. The API refuses org template writes with 403. Oracle, switch on: the admin sees **New template** and **Duplicate** on global cards, and a manage list of the org's own templates. - **AC-14 Isolation.** Oracle: org A creates a template. Org B's members don't see it in the gallery, search or the API (404 by id). Expected: the operator sees it only when acting in org A. Turning the switch off later hides org A's templates from the gallery and from Start without deleting them. Automations that reference them stop and say why. - **AC-20 Live on dev.** AC-1 through AC-19 pass on `dev.jaah.ai` after the deploy. Nothing global sits behind a flag that is off in the dev deployment. What "API half" means here: the UI parts of these ACs belong to later children (C3, C4, C5). This child proves each AC's server behaviour with API tests and with authenticated API calls on app.dev.jaah.ai. Credentials & reach: - UI ACs as the operator (AC-1–5, 17, 19, 20) | `me/jaah/debug/main/operator-login` (operator identity; switches between the operator and tenants), environment `https://app.dev.jaah.ai` | **verified**: the credential is on the roster and `credential_get` succeeded; an HTTPS GET of app.dev.jaah.ai returned 200 from this node. `dev.jaah.ai` (internal ALB) timed out from here, so oracles use `app.dev.jaah.ai` - Org A / org B isolation and switch on/off (AC-13, 14) | Same operator login (acts in any tenant; the admin covers the org admin) + `org/console/dev-orgb-login` as org B's own admin | **verified**: both `credential_get` calls succeeded. Note: the org B row's stored base_url names a retired host; use app.dev.jaah.ai Out of scope: - N-1 No change to Playbooks (`apps/web/src/pages/playbooks/`, `apps/api/app/playbook_*.py`, `routes/playbooks.py`) and no template↔playbook link. - N-2 No change to the task lifecycle, `start_task` preconditions, or persona prompts. Start reuses the existing create and start routes. - N-3 No fill-in inputs, `{{placeholders}}` or variables in template text (D3). The automation `title_template` renderer (`apps/api/app/trigger_template.py`) is untouched and not reused for templates. - N-4 No public or community marketplace: no cross-org sharing, creator profiles, ratings, paid templates, or "Recommended for you" personalization. - N-5 No version history or revert for templates. - N-6 Templates carry no project, feature, assignee, Jira key, priority or playbook (D7). - N-7 No AI generation of templates or images at runtime. - Do NOT touch: the `/assets` upload and content routes, the reaper, and the real copy bodies (C1b); starter content and its seed (C2); any `apps/web` UI (C3, C4); automation actions (C5). MCP `task_create` with a template id is deferred. Done means: - One migration, additive only, applies on a fresh DB and on develop's head. `alembic heads` shows a single head. - API tests cover: - AC-3: a draft is absent from `GET /api/templates` and present in `/manage`; once published, it is present. - AC-4: delete keeps `tasks.template_name`; `refs` then returns `deleted`. - AC-8: the counter bumps on a person's create and not on a system create; `starts_90d` is a 90-day window. - AC-9 and AC-10: `POST /api/tasks {template_id}` copies the fields, sets the link and counts once; an unpublished or invisible template gives 422 `Template unavailable`. - AC-11: favorites are per user. - AC-12: "flak" matches "flaky", the best match ranks first, and nothing matches → `[]`. - AC-13: switch off → 403 on org writes; switch on → allowed. - AC-14: as org B, an org A template is absent from list and search, 404 on get, 404 on favorite, and `refs` returns `unavailable` with no name. - The composite FK delete nulls only `org_template_id`, never `org_id`. A test proves it. - The `tools/community_edition` check is green. - AC-20 (API half): after the deploy, authenticated `GET /api/templates` and `GET /api/templates/manage` on app.dev.jaah.ai return 200 with the pinned shapes.

Outcome

Pull request

Activity

  • Draft
  • Ready
  • Runner started