Releases: gopherium/AlphOne
Release list
AlphOne 0.18.0
People can now be invited by email, and anyone who forgets their password can reset it themselves.
You will want to set up a mail relay for this release. Without one, AlphOne still runs and invitations still work, but you have to copy each activation link out of the screen and pass it on by hand, and password reset cannot be used at all.
What is new
Invite someone by email address and they receive a link to choose their own password. Administrators no longer set a password on behalf of a new person, so nobody but the account holder ever knows it. A pending invitation can be resent, which replaces the earlier link.
The login screen now offers a password reset. The person receives a link by email and chooses a new password, which signs out every other session that account had open.
Asking for a reset again sends another link, and the earlier one keeps working. This means a reset email lost to a spam folder is fixed by simply asking again. Three links may be waiting at once, and using any one of them retires the rest.
The reset form answers exactly the same way for every address, whether or not an account exists and whether or not the email was delivered. It cannot be used to find out who has an account here. Delivery problems are written to the server log instead.
Setting up mail
Set ALPHONE_SMTP_HOST, ALPHONE_SMTP_FROM and ALPHONE_PUBLIC_URL. The public address is the one your users reach AlphOne at, and it must be a plain site address with no path, query or fragment, because the emailed links are built from it.
ALPHONE_SMTP_PORT defaults to 587 and ALPHONE_SMTP_TLS to mandatory. Set a username and password if your relay needs them. Everything else is optional: link lifetimes, how many reset links may stand at once, how often one address may be mailed, and a directory of your own email templates. The full list with defaults is in .env.example.
Upgrading
Migrations run when the container starts. Take a backup first.
This release adds a table for invitation and reset links, and marks every existing account as already activated, so everyone can sign in exactly as before. No existing data is changed or moved.
AlphOne 0.17.0
Nothing changes for your install. Everything keeps working as before, and there is nothing new to set up.
This release is internal groundwork. Data is now stored with clearer ownership, and WhatsApp routing and live updates follow the same shape. You should not notice a difference.
Upgrading
Migrations run when the container starts. Take a backup first. This release rewrites some database keys rather than only adding columns.
Core migrations 00016 to 00018 run alongside the bundled plugin migrations. Your existing rows are handled automatically, so nothing moves and nothing is lost.
AlphOne 0.16.0
Nothing changes for the language you already read. English stays the default, and an account that picks nothing keeps it.
What changes is that the interface is no longer English only. Each account chooses its language on the new Language screen, the choice is stored against that account rather than the installation, and the whole interface answers in it from the next page load. Spanish ships complete, so a Spanish reader gets the navigation, the screens, the buttons, the empty states and the error messages in Spanish.
Every plugin keeps its own catalogue and its own translation project, so a plugin ships translations without touching the core and a new plugin arrives translatable from birth. The plugins that ship with AlphOne, custom fields, the contact importer and WhatsApp, are all translated.
Translating needs no Git and no code. The projects are open on POEditor, linked from Translate AlphOne in the documentation, and a weekly job collects whatever translators finish and opens one pull request for a maintainer to review.
Changes that may affect existing integrations
The graph answers locale and takes setLocale(locale: String!), both under the meta scope. locale is public and answers the default before a reader signs in, while setLocale needs an account. An unsupported language is refused rather than stored.
Errors now carry a stable reason in their extensions beside the coarse code, and some carry a meta object naming the values the reason refers to. The message text still reads as before, so anything matching on it keeps working, but match on reason instead because it is the part that will not move. Read the reason catalogue in the documentation for the full list.
The server answers in English whatever language the reader chose. It holds no catalogue of its own, and the interface turns the reason into a sentence in the reader's language, so an integration reading the graph sees the same text it always did.
Upgrading
This is a normal migration and runs on container start. Migration 00015 creates core.user_settings, which holds each account's language beside any later per-account setting. Rolling it back drops the table and every stored language choice with it, so take a backup first. See Updates and backups.
Adding a language needs a small change in AlphOne itself, because the server keeps its own list of the languages a reader may pick. Ask a maintainer rather than only translating on the platform.
AlphOne 0.15.0
Nothing changes for the accounts you already have. Every one keeps the role it holds, and a stock install still names admin and member. An account that held no role becomes a member.
What changes is that the list is no longer fixed. A plugin can declare a role of its own, or add a capability to a role that already exists, so a deployment can name roles the core knows nothing about.
A role is now a set of named capabilities rather than a rank. The core knows one capability, manage_users, and createUser, setUserDisabled and setUserRole need it. Ask me for capabilities and grantable rather than reading the role name, because a plugin role holding manage_users passes the same check an admin does.
An admin can only give a role it holds itself, and can only touch an account whose role it holds. So an admin cannot grant a role reaching further than admin, and cannot demote or disable an account that already holds one. The Users screen offers only the roles you may actually give.
An account holding no role still works contacts and tasks, because no field of the product asks for a capability. It only loses user management. alphone grantrole -role member gives a role to every account holding none.
Plugin screens can now read the signed-in session and ask what the account may do, rather than comparing a role name.
Changes that may affect existing integrations
Refusals now carry a capability extension naming what the role fell short of. The message still reads admin required, so anything matching on it keeps working, but read the extension instead of the message.
createUser takes an optional role and starts an account at the narrowest role the deployment names, which is member in a stock install.
Upgrading
This is a normal migration and runs on container start. Migration 00014 moves each role onto the account row and drops core.user_roles. Rolling it back restores the table with every role intact, including any a plugin declared, but take a backup first anyway. See Updates and backups.
AlphOne 0.14.0
Every user you already have becomes an admin when you upgrade. Nothing changes for them, because that is the authority they had. From here on, accounts created through the app arrive as members.
An admin manages users. A member works the product, which is contacts, tasks, and whatever your plugins add. createUser, setUserDisabled and the new setUserRole are reserved to admins, and a member calling one is refused with admin required. Listing users stays open to members so assigning a task to a colleague still works. Promote and demote from the Users screen, where an admin now sees a role column and a member sees the list read only.
A deployment always keeps one admin. Demoting or disabling the last enabled admin is refused, so you cannot lock yourself out.
A role narrows the user, a scope narrows what a token carries of that user's authority, and an operation runs only when both allow it. A token can no longer reach further than the person who created it, whatever its scopes.
Changes that may affect existing integrations
Minting a token now refuses an area the schema does not declare, so a typo like contact:read fails at mint instead of quietly producing a token that can never act. Area names are case sensitive, so TASKS:read is refused where it used to be accepted.
The WhatsApp media download now needs whatsapp:read, and a token scoped elsewhere gets 403. Run alphone token list to see what yours hold and mint replacements where needed.
A plugin can now hold its routes to one scope area, which is what closes that download. The WhatsApp webhook stays public, so inbound messages keep arriving.
Upgrading
This is a normal migration and runs on container start. Rolling migration 00013 back and reapplying it re-grants admin to every user, forfeiting every demotion you have made, so do not roll it back to undo a role change. See Updates and backups for how to save the roles table first.
AlphOne 0.13.0
Every API token is now scoped and mortal. A token carries a set of scopes checked on each operation and an expiry that ends it, and its owner mints, lists and revokes tokens from the app rather than a shell. Existing tokens keep full scope
and no expiry until they are replaced.
A second plugin root named enterprise ships empty under its own license, so a closed plugin can join a build without editing public files.
- API tokens carry scopes, so a connector reaches only the areas it was granted
- API tokens expire, ninety days by default from the command line
- A Tokens screen mints, lists and revokes them, showing each secret once
- Token management needs a login session, so a leaked token cannot mint another
- A second plugin root reads closed plugins without editing public files
- The route outline composes from plugin exports rather than a hardcoded map
Upgrading needs no action. The migration grants every existing token full scope and no expiry, and refusals name the scope they needed, so a token minted before this release keeps working exactly as it did.
AlphOne 0.12.0
Every caller stands in a tenant.
Added
core.tenantsandcore.tenant_members, with one tenant named Default seeded by the migration. Every user belongs to it and the graph answers each caller its own tenant.Query.tenant, answering the caller's own tenant with no argument, derived from the authenticated identity.
Notes and fixes
- Nothing is enrolled anywhere. A user with no membership row is answered the default, so there is no backfill, nothing to run at the call sites, and no path that can forget a user. Rows exist only for users deliberately placed elsewhere.
- The fallback lives in the SQL. One left join ordered by membership presence returns either the placed tenant or the default in a single round trip.
- Resolution happens in the resolver rather than in middleware, so the session, the API token and the in process MCP paths all reach it.
- An upgraded install needs no action. The migration adds the tables and the default tenant, reads nothing across schemas, and every existing user is answered the default from the first request.
- Tenant management is not in this release. Creating, renaming and moving members lives outside core.
- The build moves to Go 1.26.6, clearing seven standard library advisories reported against 1.26.5.
AlphOne 0.11.0
A spreadsheet column fills a field you defined yourself.
Added
- Import mapping onto fields. A CSV or Excel column maps onto any field an operator defined, beside Name, Email and Phone, and the values arrive with the contacts. See Fields.
sdk.FieldProviderandsdk.FieldConsumer, the seam a plugin serves fields through. The host collects providers after registration and hands them to consumers, in the server and in the seed. A plugin never imports another plugin.- The demo import carries a Birth date column, so a fresh
make seedshows the feature end to end.
Notes
- The fields plugin reads the text. A cell arrives as text and the value store holds typed values, so the plugin that owns kind semantics owns the reading too. Numbers take an optional sign and digits, booleans take true, yes, 1, false, no and 0 in any letter case, and dates take YYYY-MM-DD exactly.
- Cells are checked before a contact is created. A cell that does not fit its field fails that row, the reason names the field and its kind, and no contact appears.
- An import creates, it never updates. A row matching a contact you already have is skipped and that contact's fields are left alone.
- A mapping naming a field that was archived after mapping refuses the commit and leaves the import editable, so you can map it again.
- With no plugin serving fields the importer offers the three core columns exactly as before.
- Dependency updates now come from the pnpm workspace root, so a grouped update carries the lockfile with the manifests.
@wordpress/element8.5.0 carries the React 19 fix upstream, so the local patch is gone.- The development database runs with
max_connections=300. The test suite reached the previous ceiling of 100 and failed intermittently.
AlphOne 0.10.0
AlphOne lets an operator define contact fields at runtime, with no rebuild and no release.
Added
- Contact fields defined from the Fields screen on a running server, in six kinds: Text, Long text, Number, Yes or no, Date and Choice. See Fields.
- Every field becomes a real field on
Contactin the GraphQL API under the name you chose, so{ contacts { edges { node { name birthDate } } } }answers a moment after you save it. - A Fields panel on the contact screen edits the values, each checked against the kind its definition declares.
- Archiving a field takes it out of the schema and the screen at once and keeps its stored values, so defining it again with the same name and kind brings them back.
- A plugin can contribute a panel to the contact screen through the frontend SDK.
make dev-watchrebuilds and restarts the server on save.
Notes
internal/dyngraphwidens the compiled schema with the fields a plugin declares and rewrites each incoming query onto a carrier field the generated executor already knows.- Core keeps
nameas the one fixed contact field. The catalogue, the values, the admin screen and the seed are all thefieldsplugin. - The kind cannot be changed once a field exists, because stored values would no longer fit.
- Choice holds a short line for now. A fixed option list is a later release.
- The plugin migrates itself on start, so an existing install needs no action.
- Sorting or filtering the contact list by a field, and importing CSV columns onto fields, are not in this release.
AlphOne 0.9.0
AlphOne answers AI agents over MCP (Model Context Protocol).
Added
POST /api/mcpserves four read only tools behind the samea1_API tokens:workload_summary,list_my_tasks,find_contactsandget_contact. Each runs a curated GraphQL operation in process, so auth, scoping and the complexity cap stay in the graph. See AI agents.alphone help,alphone -handalphone --helpprint usage and exit.
Fixed
- An unknown subcommand is refused instead of quietly starting a server.
- Capped counts follow the graph's own paging, so exactly 200 open tasks no longer reads as capped.
Notes
- Tools answer structured data, never a written sentence. The wording of an answer is the agent's.
- Task lists carry only the token owner's work. Contact answers span every user, the same way the contact page does, and each task names its assignee.
- Claude Code and the Agent SDK connect today. The claude.ai and Claude Desktop connectors need OAuth (Open Authorization), which is not in this release.
- Gherkin specification introduced in the project, driven by godog against a real server on a real database.