📋 Pre-flight Checks
📝 Bug Description
The /admin/* JSON API is inconsistent: adminTokenMetadata (served by the token endpoints) already carries snake_case json: tags (principal_id, token_prefix, created_at, ...), but HumanUser and ProjectGrant (served by GET /admin/users and GET /admin/users/{id}/grants) have no json: tags at all, so they serialize with Go-default PascalCase field names (PrincipalID, DisplayName, GrantedByPrincipalID, ...).
This breaks the stable, documented snake_case wire contract for programmatic clients hitting the admin API, and is inconsistent with every other admin response shape (including the request bodies, e.g. createAdminUserRequest, which already use snake_case tags).
🔄 Steps to Reproduce
- Start engram with an admin-enabled cloud server.
curl -H "Authorization: Bearer <admin-token>" http://localhost:PORT/admin/users
- Inspect the JSON body.
✅ Expected Behavior
GET /admin/users and GET /admin/users/{id}/grants return snake_case field names, consistent with adminTokenMetadata and with every admin request body, e.g.:
[{"principal_id":"p-...","username":"alice","email":"...","display_name":"Alice","role":"member","enabled":true,"created_at":"..."}]
❌ Actual Behavior
They currently return Go-default PascalCase field names, e.g.:
[{"PrincipalID":"p-...","Username":"alice","Email":"...","DisplayName":"Alice","Role":"member","Enabled":true,"CreatedAt":"..."}]
Source: internal/cloud/cloudstore/identity.go — HumanUser (~line 58) and ProjectGrant (~line 97) have no json: tags, while adminTokenMetadata in internal/cloud/cloudserver/admin_handlers.go (~line 65) does.
Operating System
Linux (Other)
Engram Version
v1.19.0
Agent / Client
Other
💡 Additional Context
I have a fix ready (adding snake_case json: tags to both structs matching the adminTokenMetadata convention, plus wire-shape assertions in admin_handlers_test.go) and would like to open a PR once this is approved. Verified no other consumer (dashboard templ components, existing tests) depends on the current PascalCase JSON output — all in-repo consumers use direct Go field access or round-trip json.Unmarshal into the same struct, so this is a non-breaking, additive fix in practice.
📋 Pre-flight Checks
status:approvedbefore a PR can be opened📝 Bug Description
The
/admin/*JSON API is inconsistent:adminTokenMetadata(served by the token endpoints) already carries snake_casejson:tags (principal_id,token_prefix,created_at, ...), butHumanUserandProjectGrant(served byGET /admin/usersandGET /admin/users/{id}/grants) have nojson:tags at all, so they serialize with Go-default PascalCase field names (PrincipalID,DisplayName,GrantedByPrincipalID, ...).This breaks the stable, documented snake_case wire contract for programmatic clients hitting the admin API, and is inconsistent with every other admin response shape (including the request bodies, e.g.
createAdminUserRequest, which already use snake_case tags).🔄 Steps to Reproduce
curl -H "Authorization: Bearer <admin-token>" http://localhost:PORT/admin/users✅ Expected Behavior
GET /admin/usersandGET /admin/users/{id}/grantsreturn snake_case field names, consistent withadminTokenMetadataand with every admin request body, e.g.:[{"principal_id":"p-...","username":"alice","email":"...","display_name":"Alice","role":"member","enabled":true,"created_at":"..."}]❌ Actual Behavior
They currently return Go-default PascalCase field names, e.g.:
[{"PrincipalID":"p-...","Username":"alice","Email":"...","DisplayName":"Alice","Role":"member","Enabled":true,"CreatedAt":"..."}]Source:
internal/cloud/cloudstore/identity.go—HumanUser(~line 58) andProjectGrant(~line 97) have nojson:tags, whileadminTokenMetadataininternal/cloud/cloudserver/admin_handlers.go(~line 65) does.Operating System
Linux (Other)
Engram Version
v1.19.0
Agent / Client
Other
💡 Additional Context
I have a fix ready (adding snake_case
json:tags to both structs matching theadminTokenMetadataconvention, plus wire-shape assertions inadmin_handlers_test.go) and would like to open a PR once this is approved. Verified no other consumer (dashboardtemplcomponents, existing tests) depends on the current PascalCase JSON output — all in-repo consumers use direct Go field access or round-tripjson.Unmarshalinto the same struct, so this is a non-breaking, additive fix in practice.