Roadmap
Status: Accepted (2026-09-14) · Replaces: scope-v1.md
apistock ships through pre-release milestones. Each one is usable on its own and has a definition of done. Nothing outside a milestone's scope starts without an accepted ADR. Everything is v0 until 1.0 (ADR-0015).
Before v0.1: Architecture gate#
| Item | Status |
|---|---|
| Architecture v2 and ADRs 0014–0030 | Done |
| OpenAPI spike: code-first with Huma (ADR-0027, results) | Done |
| Anchor-edit spike: text insertion wins (ADR-0021, results) | Done |
| First-run spike: Minimal in 12.0 s cold, 1.6 s warm (ADR-0028, results) | Done |
| Merge spike (ADR-0016) | Done |
v0.1: Foundation#
Status: implemented and tested, not released (2026-09-14). Results below.
| Delivers | Core packages (app, httpx, health, actor, requestid, audit, mail, config, page, ratelimit, buildinfo), modules/openapi (Huma, problem errors, embedded Scalar), modules/telemetry (OpenTelemetry, correlated logs), hand-written examples/minimal, Minimal recipe generated from it, aps new (Minimal), aps dev (reload, .env, port check), aps version, security defaults, Dockerfile, project CI (tests on Go 1.26/1.27, race, golangci-lint, govulncheck, gitleaks, dependency budget, template and OpenAPI drift, end-to-end), signed release workflow for aps |
| Results | First run with the real CLI (scripts/first-run.sh): 25.0 s from clean caches, 4.8 s warm. Generated app passes its own tests. Lint: 0 issues in all modules. gorelease starts at the first tag. |
| Not included | Database, auth, Full/Custom presets, Docker services |
| Done when | On a clean machine: go install → aps new my-api → aps dev → /docs in under 60 seconds; generator reproduces examples/minimal exactly; threat model rows 2–10 and 21 addressed |
v0.2: Data and identity#
Status: implemented and tested, not released (2026-09-15). Done (2026-09-14): modules/postgres (pool with tracing, DBTX, InTx, error classification, goose migrations with advisory lock, readiness check, pgtest against Docker PostgreSQL), modules/settings and core config.Value[T] (typed declarations, PostgreSQL store with version checks, history and audit events, LISTEN/NOTIFY with periodic resync; ADR-0031), ADR-0032 repository style; modules/jobs library (River client with context propagation and graceful stop, job definitions with runtime-editable config and schedules, Manager for the admin panel, AsyncSender, River migrations; ADR-0033). examples/full-single golden app wiring postgres, runtime settings and jobs, with cmd/migrate, compose.yaml and the /ops/settings, /ops/jobs/* and /ops/queues admin APIs behind an interim OPS_TOKEN (replaced by platform roles when modules/auth lands). aps gen job and interactive aps new, with every prompt available as a flag (ADR-0035). modules/auditpg (append-only audit_events table, Record and transactional RecordTx, metadata redaction and size bounds, filtered and paginated List, Get; ADR-0036), wired into examples/full-single with GET /ops/audit and GET /ops/audit/{id} (moved from v0.5). Email: modules/mail/smtp and modules/mail/resend, core mail.ErrRejected and mail.WithDefaults, rejected sends cancelled by the mail worker, Mailpit in development, sender as runtime settings, GET /ops/mail and POST /ops/mail/test, and interactive aps add mail to choose or switch Resend or SMTP (ADR-0037). Authentication (2026-09-15, ADR-0038): modules/auth building blocks (argon2id hashing, tokens and codes stored as hashes, session middleware and cookies, permission catalog, emails) and, in examples/full-single, an app-owned internal/modules/auth with domain, use cases, repository (one SQL file per operation) and /v1/auth endpoints: register, email codes, login with cookie or bearer token, logout and logout-all, sessions, password reset and change, account deletion, platform roles (platform_admin, ops_viewer) granted with go run ./cmd/api grant-role, and the auth_cleanup job. OPS_TOKEN is gone: /ops/* uses sessions and roles. Example resource (2026-09-15, ADR-0039): internal/modules/projects in examples/full-single, owned by the signed-in user, with all four layers, its own projects table and repository, keyset pagination through page, versioned PATCH, audit events, cross-owner isolation tests and an end-to-end test; the template for aps gen resource. aps gen resource (2026-09-15): string, text and enum fields with unique and filter options, interactive or flags, reproduces the projects module byte for byte and is tested by generating other resources into a copy of the app and running their tests. Release tracking (2026-09-15, ADR-0040): modules/releases records each instance's build at start with a heartbeat and clean-stop marking, derives releases, and prunes old instances; examples/full-single runs the tracker as a worker and serves GET /ops/releases, /ops/releases/current and /ops/releases/instances (moved from v0.5). aps new --preset=full (2026-09-15, ADR-0041): templates generated from examples/full-single and checked byte for byte, go.mod derived from each golden app's go.mod for both presets, a leak check for repository paths, the app's database named after the app; a new Full app passes its own tests and accepts aps gen resource and aps gen job. aps gen migration (2026-09-15): an empty forward-only migration that runs after the existing ones, named in any case, with the same flags as the other generators; tested end to end by generating one into a new Full app, adding a column to a generated resource's table and running the app's tests. Seed data (2026-09-15, ADR-0042): cmd/seed creates admin@example.com with platform_admin, a random password printed once and never stored, and three example projects, through the modules' use cases; development only and safe to run again. aps dev with Docker (2026-09-15, ADR-0028): .env from .env.example, Docker and host port checks naming the .env line to change, docker compose up -d --wait, migrations (again when a migration changes) and seed data before the app starts; --no-services; --observability starts Grafana (grafana/otel-lgtm, a Compose profile in both presets) and points the app's OpenTelemetry export at it. The Custom preset moved to v0.5 and the tenancy prompt to v0.4.
| Delivers | modules/postgres, modules/settings (runtime settings declared in code, stored in PostgreSQL, live on every instance, /ops/settings API, config.Value[T] in core; ADR-0031), modules/jobs (River, AsyncSender, job definitions with runtime-editable schedule, enabled, timeout and retries; /ops/jobs admin APIs; ADR-0033), modules/mail/resend and mail/smtp, modules/auditpg, modules/auth (email/password, email codes, sessions, logout-all, reset/change password, active sessions, delete account, platform roles, permission catalog), modules/releases, Full preset with single-tenant generation, aps dev with Docker (PostgreSQL, Mailpit, migrations and seed, --observability Grafana), aps gen resource, aps gen job and aps gen migration, seed data, examples/full-single |
| Not included | Custom preset (v0.5), tenancy prompt (v0.4), social login, 2FA, passkeys, multi-tenant, ops APIs other than /ops/settings, /ops/jobs/*, /ops/queues, /ops/audit, /ops/releases and /ops/mail (audit stats and retention stay in v0.5) |
| Done when | From a fresh aps new --preset=full: register → verify email code → login → role-protected endpoint → audit event recorded, all in e2e tests; a setting changed through /ops/settings on one instance is served by a second instance without restart and appears in history and the audit log; a job generated with aps gen job can be rescheduled, disabled and run now through /ops/jobs without a restart; nullable org_id and role-scope columns present in library tables; threat model rows 12, 13, 19, 23 and 24 addressed |
| Results | Covered by tests against Docker PostgreSQL and Mailpit: a new Full app (APS_E2E=1) passes its own suite, including sign-up, email verification, login, role-protected /ops/* endpoints with audit events, the projects resource, seed data, and a setting changed on one instance served by a second with history and an audit event; the generated heartbeat job is rescheduled, run and disabled through /ops/jobs without a restart (TestJobsThroughOps); nullable org_id columns exist in the settings, audit and auth tables; threat model rows reviewed against the code (ADR-0029). aps dev in a new Full app (APS_E2E_DOCKER=1): API ready 9.6 s after start with images and Go caches warm; a cold run with image pulls isn't measured |
v0.3: Strong authentication#
Status: done (2026-09-15; threat model rows 14–16 reviewed and marked done, every method covered by its integration tests). Two-factor authentication (2026-09-15, ADR-0043): TOTP implemented in modules/auth (RFC 6238) with an AES-256-GCM Keyring from AUTH_ENCRYPTION_KEYS and rotate-auth-keys, 10 single-use recovery codes, a sign-in challenge (POST /v1/auth/login returns 202, POST /v1/auth/login/mfa finishes), replay protection across instances, Catalog.RequireMFA and core actor.Require with step-up permissions, platform_admin and ops_viewer requiring 2FA in every environment (403 mfa_required), reset-mfa for lost devices, seed data that enrolls the administrator, and aps dev writing a development key to .env. Passkeys (2026-09-15, ADR-0044): modules/auth/passkey wrapping go-webauthn with a passkeytest software authenticator; passwordless sign-in and passkeys as a second factor; up to 10 passkeys per account with single-use ceremonies, user verification and clone detection; WEBAUTHN_* in the environment with localhost in development; /.well-known/apple-app-site-association and assetlinks.json for iOS and Android apps; a QR code image in authenticator app setup; after review, changes to sign-in methods need the password once a session's second factor is 10 minutes old, and a passkey confirms account deletion, turning off the authenticator app and replacing recovery codes. Provider setup (2026-09-15, ADR-0045): AUTH_PROVIDERS.md in every Full app and a .env.example block per method listing what developers provide and where to find it, a Sign-in methods block at start, go run ./cmd/api auth-providers and GET /ops/auth/providers. Google and Apple sign-in (2026-09-15, ADR-0046): modules/auth/social on x/oauth2 and go-oidc with a socialtest fake provider; an API-hosted web flow with state bound to a cookie, PKCE and nonce; native ID-token sign-in with server nonces; automatic linking on a verified email that removes an unverified account's password; the second factor still required; identities, Apple token revocation and notifications; APP_PUBLIC_URL. CLI look (2026-09-15, ADR-0035 v0.3 notes): prompts in the apistock theme (theme); aps new asks one question at a time and folds each answer into one line, and prints a log of finished steps ending with next:. Follow-ups outside the definition of done: sign-in against real Google and Apple accounts (needs the maintainer's credentials), and Apple token revocation with retries.
| Delivers | Google and Apple sign-in (web and native), account linking, TOTP with recovery codes, passkeys, 2FA policy per role, docs/auth-providers.md |
| Not included | GitHub login, API keys |
| Done when | Each method passes its integration suite; threat model rows 14–16 reviewed and documented |
v0.4: Organisations#
Status: done (2026-09-15; done-when checked end to end and the branch reviewed). Design accepted in ADR-0048 (2026-09-15). Done: modules/orgs (organisation IDs, RequireMember, invitation emails); account hooks in the auth module; examples/full-multi with the app-owned orgs module (organisations, one role per member, invitations for the invited verified address only, personal workspaces, soft delete, restore and the orgs_purge job), org-scoped projects with cross-organisation denial tests, and a drift check against full-single; aps new --tenancy multi; aps gen resource --scope org (the default in multi-tenant apps), reproducing full-multi's projects module; threat model rows 17, 25 and 26 done (ADR-0029). Review fixes: the purge job deletes an organisation only while its purge time has passed, so one restored and deleted again meanwhile stays, and RequireMember hides organisations when a Memberships implementation wraps ErrNotMember. aps add orgs moved to v0.5: it needs the per-feature recipes and 3-way merges that aps add and aps upgrade bring. A drift check keeps full-single and full-multi identical outside the files organisations change.
| Delivers | modules/orgs (personal workspaces, memberships, invitations, org roles, ownership transfer, soft delete), multi-tenant generation, the tenancy prompt and --tenancy flag in aps new (moved from v0.2: before organisations its only answer is single-tenant), resource/org template (aps gen resource --scope org), examples/full-multi with a drift check against full-single |
| Not included | Row-level security, subdomain tenants, per-org billing |
| Done when | Generated cross-org denial tests pass for every org-scoped resource; four isolation layers verified |
| Results | Covered by tests against Docker PostgreSQL and Mailpit. Tests: every org-scoped resource gets TestOrganisationsCantReachEachOthers<Resources> (read, update, delete and list from another organisation) from aps gen resource --scope org; CI generates two more org-scoped resources into a copy of full-multi and runs their tests, and a new multi-tenant app (APS_E2E=1) generates one and passes its suite. HTTP: routes under /v1/orgs/{orgId} answer 404 org_not_found to non-members and 404 for another organisation's row (TestProjectsEndToEnd). Code: every use case calls orgs.RequireMember first and every repository query filters on org_id (reviewed; TestRequiresMembership). Database: org_id NOT NULL with ON DELETE CASCADE, UNIQUE (org_id, id) and per-organisation uniqueness; purging an organisation removes its rows (TestPurgingAnOrganisationDeletesItsProjects). aps dev in a new multi-tenant app (APS_E2E_DOCKER=1): API ready 11.5 s after start, with the administrator's personal workspace and three example projects |
v0.5: Operations and upgrades#
Status: in progress (ADR-0050, 2026-09-15; v0.2.0, v0.3.0 and v0.4.0 tagged at their milestone commits). Done: apistock.lock v2, written by aps new and kept current by aps add mail; aps upgrade (merge base rebuilt from a checkout tag or the module proxy and proven against the lock, 3-way merges on branch aps-upgrade/<version>, go.mod, build, api/openapi.json and commit), with an end-to-end test upgrading an app created and edited with aps at v0.4.0; aps add orgs (the same merge into the multi-tenant tree, plus migrations that add organisations, give every account a personal workspace and move projects into it, proven on a PostgreSQL database with data). Next: the ops endpoints, maintenance mode, aps doctor, Postman collection and llms.txt, in their own ADR. The Custom preset moved out of v0.5.
| Delivers | /ops/* (audit stats, system health, jobs overview, retention), maintenance mode, Postman collection, llms.txt, aps upgrade (3-way merge on a branch), aps add orgs (single → multi-tenant, moved from v0.4, ADR-0048), aps doctor, apistock.lock v2 (release, template inputs and file hashes) |
| Not included | Custom preset (moved out by ADR-0050: each combination needs its own golden tree, and today's real choices are tenancy and the email provider), feature flags, live observability, incidents |
| Done when | A Full app generated with aps at v0.4.0 and edited by script (a changed line in a tracked file, a new resource, a new migration, aps add mail smtp) upgrades to v0.5 in CI with every edit kept, builds and passes its tests; the same app converted with aps add orgs has the schema of a new multi-tenant app and passes its suite; ops endpoints require platform roles and 2FA |
v1.0: Stable#
| Delivers | External security review with findings fixed, API freeze and stability tiers in force, documentation content ready for the public site (the site itself ships in v1.3, ADR-0049), domain hardening complete (including rate limits shared across instances, replacing today's per-instance limiters), governance and contribution guide |
| Done when | Security review signed off; gorelease baseline recorded; scaffold compatibility promise (ADR-0016) active |
v1.1#
Feature flags, per-org settings, live observability, incident reports, API keys and service accounts, GitHub login, PostgreSQL row-level security option, idempotency keys, custom local dev console, Resend bounce/complaint webhooks, Prometheus /metrics option.
v1.2: Client templates#
Status: proposed (ADR-0047).
| Delivers | aps new asks for a docs site, a dashboard and mobile apps (--docs, --dashboard, --mobile none|expo|native) and creates each from its own template repository, with the app name, bundle ID, API URL and sign-in methods filled in. Templates are pinned, hashed and signed archives listed in each aps release, cached locally, with --templates <path> for local checkouts. Each template declares what it accepts in apistock-template.json and calls the API through clients generated from api/openapi.json. Order: fetch and fill-in mechanism with template-docs, then template-dashboard, then template-expo (iOS and Android) |
| Not included | Native iOS and Android templates (only when builders ask for them), hybrid apps, upgrading client code with aps upgrade (client output is one-shot) |
| Done when | CI creates an app with every template against examples/full-single and builds each part; a tampered archive or unlisted version is refused; sign-in, 2FA and passkeys work from the dashboard and the Expo app; threat model rows for templates added |
v1.3: Public website#
Status: in progress, built ahead of order (ADR-0049, accepted 2026-09-15). Done: site/, a Go generator for apistock.dev (landing page) and docs.apistock.dev (guides, CLI, decision records and roadmap rendered from docs/, and an API reference with request and response examples and "Try it" rendered from examples/full-multi/api/openapi.json), with search, light and dark themes, Markdown copies, llms.txt, Cloudflare Pages _headers, and a test that fails on any broken link; the logo kit in docs/brand/logo. Generated apps' /docs in the same design: modules/openapi/reference renders every app's own endpoints from its OpenAPI document with examples, "Try it" and search under a strict Content-Security-Policy with embedded fonts, replacing the embedded Scalar; the site's API reference uses the same renderer. Still to do: publishing on Cloudflare Pages, versioned docs, and compiling the site's code snippets.
| Delivers | apistock.dev landing page; docs.apistock.dev framework documentation (guides, modules, CLI, decision records, changelog) with search, a version per minor release, copy as Markdown and llms.txt; a public API reference at docs.apistock.dev/api-reference rendered from examples/full-multi/api/openapi.json; all three in the apistock look (theme) with Mintlify's page structure; generated apps' /docs restyled with the same system in the app's own name and accent. Built by a small Go generator in site/ and published on Cloudflare Pages |
| Not included | Hosting docs for developers' own apps, a blog, community module index pages (Later), translations |
| Done when | CI builds the site and fails on broken links, WCAG 2.2 AA failures in both themes and code snippets that no longer compile or match the golden apps; the public API reference and a new app's /docs render the same openapi.json with the same theme; the landing page and a docs page reach Largest Contentful Paint within 2.5 s on a mid-range phone over 4G |
Later#
Native iOS (SwiftUI) and Android (Kotlin) client templates, community module index and author tooling, subdomain tenant resolution, per-org quotas and billing, storage and outgoing webhooks modules, enterprise SSO integration, WASI-sandboxed generators.
Not planned#
Admin web UI inside generated apps (a dashboard comes as a client template instead), hosted control plane, hybrid mobile apps, databases other than PostgreSQL, schema- or database-per-tenant, custom router, ORM or DI container, plugin runtime, secrets or infrastructure configuration stored in the database, storing application logs in PostgreSQL.