v0.2.0
Who should read this release
- End users:
- Client app developers:
- Apps now name the target group with an explicit
repofield instead of overloading the service-auth token's audience. - A group's owner can now remove the group from the service.
- You can now turn an existing account into a group, instead of always creating a brand-new one.
- The health check now reports the running service version, and the same check is also reachable at
/xrpc/_health.
- Apps now name the target group with an explicit
- Operators:
- Apps now name the target group with an explicit
repofield instead of overloading the service-auth token's audience. - A group's owner can now remove the group from the service.
- You can now turn an existing account into a group, instead of always creating a brand-new one.
- The health check now reports the running service version, and the same check is also reachable at
/xrpc/_health.
- Apps now name the target group with an explicit
Minor Changes
-
#33
677298eThanks @aspiers! - Apps now name the target group with an explicitrepofield instead of overloading the service-auth token's audience.Affects: Client app developers, Operators
Client app developers: group-scoped methods take a
repofield (a handle or DID) naming the target group, with the JWTaudset to the service DID — the shape a stock@atproto/apiclient already emits. The old form (group inaud, norepo) still works but is deprecated.Legacy (deprecated) New (supported) Group named by JWT audexplicit repoJWT audthe group DID the service DID repofieldabsent present Deprecation header Deprecation: true+Linknone - Behaviour change to adapt to now:
repois the group selector, not a cross-check — the old403"repo field must match the group DID" is gone; areponaming no registered group returns401 Unknown group. RBAC is unchanged: you can only target groups you have a role in. - How to migrate (per-method
repoplacement, the coupledrepo+audswitch, non-proxied vs proxied calls, detecting un-migrated calls): seedocs/aud-migration.md. Design rationale and security analysis:docs/design/aud-deprecation.md.
Operators: no new environment variables and no migration;
SERVICE_DIDis unchanged. The service now serves itsdid:webdocument atGET /.well-known/did.json(a public, unauthenticated route, sibling to/health); it must be publicly reachable, since that is how the service DID resolves and how service proxying targets the service. A rate-limitedwarnlog flags any client still on the legacy form — one line per caller per 15 minutes. - Behaviour change to adapt to now:
-
#30
e4a0aacThanks @aspiers! - A group's owner can now remove the group from the service.Affects: End users, Client app developers, Operators
End users: if you are a group's owner, you can now delete the group from the service. This removes the group and its membership from the service only — the underlying account and its data are left untouched, so the account continues to exist and could be added back later. Only the owner can do this; admins and members cannot.
Client app developers: a new procedure
app.certified.group.destroyremoves a group from the service, gated on theownerrole (new RBAC operationgroup.destroy). It removes only the service's record of the group; the underlying PDS account is left intact, so the account can be re-imported afterwards viaapp.certified.group.import. Call it group-scoped, the same auth style as the other per-group methods. Full request/response and errors are indocs/api-reference.mdanddocs/integration-guide.md.Heads-up: like the other per-group methods,
destroycurrently names the target group via the JWTaud. That overload is being deprecated (issue #27) in favour of an explicit request-level group field, withaudreverting to the group service's own DID. Theaud= group DID form will be supported during a transition window and then removed — build against the explicit-group form once it ships.Operators:
destroyis served on/xrpc/app.certified.group.destroy; no new environment variables. It deletes the group'sgroupsrow andmember_indexentries (in one transaction) and then the per-group SQLite file underdata/groups/. The destroy is recorded in the service log, not in the (deleted) per-group audit log. -
#30
f265075Thanks @aspiers! - You can now turn an existing account into a group, instead of always creating a brand-new one.Affects: Client app developers, Operators
Client app developers: a new procedure
app.certified.group.importis the sibling ofapp.certified.group.register— it reuses an existing account instead of creating one. You supply the account's app password so the service can act on its behalf, and the JWT must be signed by the account being imported (iss= the account's DID), not by the prospective owner. Two consequences worth knowing: the service holds no recovery key for an imported account (the owner's own credentials are their credible exit), andimportdoes not modify the account's DID document. Full request/response, auth model, and errors are indocs/api-reference.md(Group lifecycle),docs/integration-guide.md(Step 1b), anddocs/design/group-import.md.Operators:
importis served on/xrpc/app.certified.group.import(service-auth, likeregister); no new environment variables. Imported groups are stored in thegroupstable withencrypted_recovery_keyleftNULL, distinguishing them from registered groups, and are driven via the per-grouppds_urlresolved at import time — which may differ fromGROUP_PDS_URL. -
#35
a906bd8Thanks @aspiers! - The health check now reports the running service version, and the same check is also reachable at/xrpc/_health.Affects: Client app developers, Operators
Client app developers:
GET /healthresponse gains two fields:service(always"group-service") andversion(e.g."0.1.0+90d10b96"). The existingstatus: "ok"/503 { status: "error", message: "database unreachable" }behaviour is unchanged, so existing health checks keep working.- A new
GET /xrpc/_healthroute returns the identical body to/health(including the same 503-on-DB-failure semantics). This mirrors the upstream PDS convention of exposing/xrpc/_health; the group service has no upstream PDS, so it serves the route itself. Note it returns the full{ status, service, version }object, not the bare{ version }some atproto services return.
Operators:
- The reported version resolves in this order: the
CGS_VERSIONenv var, then a.cgs-versionfile written at image-build time, then theversionfield inpackage.json. SetCGS_VERSIONto override the stamp (e.g.CGS_VERSION=0.1.0+abcdef01). - On Railway, the Docker build stamps
<package.json version>+<short commit sha>automatically fromRAILWAY_GIT_COMMIT_SHA— no action needed. - For local
docker build, run./scripts/stamp-version.shfirst to write.cgs-version; the build fails withERROR: .cgs-version not foundotherwise..cgs-versionis gitignored.