Migration — camelCase fields
Hub's HTTP API, domain types and npm packages moved every field name from snake_case to camelCase. Database column names did not change.
This is a breaking change. There are no snake_case aliases and no deprecation window — and, importantly, almost nothing errors: an old name is silently dropped by the query parser or silently ignored by the validator, depending on where it appears. Do not expect a 4xx to find these for you.
Tracks #1501.
What changed
realm_id → realmId, created_at → createdAt, display_name → displayName, and so on for every multi-word field. The rename applies uniformly to:
- Response bodies — every entity field, in both the
{ data, meta }record envelope and collections. - Request bodies —
POST/PATCHpayloads for every entity. - Query parameters — the rapiq vocabulary:
fields,filter,sort,include. Relation names move too (include=master_image→include=masterImage). meta.schema— the published allow-lists now list camelCase keys.- Telemetry log labels —
ref_type/ref_id→refType/refId(LogFlag), and alongside themactor_type/actor_id→actorType/actorId,target_type/target_id→targetType/targetId, andbucket_type→bucketType. - The npm packages —
@privateaim/core-kit,core-http-kit,storage-kit,telemetry-kit,messenger-kit,messenger-http-kit,client-vue.
Before / after
GET /nodes?filter[realm_id]=<id>&sort=-created_at&fields=%2Bexternal_name&include=registry_projectGET /nodes?filter[realmId]=<id>&sort=-createdAt&fields=%2BexternalName&include=registryProject// before
{ "data": { "id": "…", "display_name": "Node A", "realm_id": "…", "created_at": "…" }, "meta": {} }
// after
{ "data": { "id": "…", "displayName": "Node A", "realmId": "…", "createdAt": "…" }, "meta": {} }What did NOT change
| Surface | Still snake_case |
|---|---|
| Database columns | realm_id, created_at, … — pinned per column, unchanged |
| Permission names | analysis_create, node_update, registry_manage, … |
| OAuth2 / OIDC parameters | client_id, client_secret, grant_type, redirect_uri, code_verifier |
| Token introspection payloads | realm_id, sub_kind, sub_name, … |
| Environment variables | DB_TYPE, AUTHUP_URL, MINIO_*, … |
| Table names | analysis_nodes, registry_projects, … |
Permission names are unchanged because they are stored in Authup, so nothing has to be re-provisioned.
Upgrading
API and npm consumers
Rename the fields you send and read. meta.schema on any query-capable GET is the authoritative list of accepted fields / filter / sort / include keys for that endpoint — query it if you are unsure:
GET /nodes?pagination[limit]=1Failure modes to expect while migrating:
- An unknown filter, sort or
includekey is silently dropped, not rejected. rapiq'sthrowOnFailureis deliberately not enabled, so a stalefilter[realm_id]does not fail — the filter is pruned and the endpoint answers with a wider, unfiltered result set. This is the failure mode to watch for: it looks like success. (strict: trueon hub's schemas does not change this — it governs parameters that declare no allow-list, and every hub schema declares one.) - An unknown request-body key is dropped by the validator, so a write appears to succeed while leaving the field unset. Check the response body.
- An unknown
fieldsentry does not error; the field is simply absent from the response.
Because none of these raise, diff every key you send against meta.schema rather than waiting for an error.
Database
No action. No migration ships with this change: every renamed property carries an explicit column name, so the physical schema is byte-identical. A run → revert → run round-trip over the existing migrations is unaffected.
Telemetry / logs
Log lines written before the upgrade carry ref_type / ref_id labels and are not selectable under the new refType / refId names — VictoriaLogs stores label keys verbatim. Entity views (the analysis log panel) will therefore show only post-upgrade lines. Existing rows are not rewritten; they age out with your retention policy. Query old lines directly by their old label names if you need them.
Anything outside Hub that writes logs (node-side components posting to /logs or /analysis-node-logs) must switch label names in lockstep.
Node-side components
These flat, non-envelope endpoints changed field names and need coordinated node updates:
POST /analysis-node-logs— body keysanalysis_id,node_id,node_realm_id,analysis_realm_id→ camelCase.GET/POST /nodes/:id/client/credentials—display_name→displayName, in the response and in thePOSTrequest body. The body has no validator, so a legacydisplay_namekey is silently dropped: the secret still rotates and the response is200, while the display name stays unchanged.GET/POST /analyses/:id/client/credentials— responsedisplay_name→displayName. ThePOSTbody ({ secret }) is unchanged. Node-callable: a client whose node holds an approvedanalysisNoderow may read these.GET /nodes/:id/registry/credentials—account_name,account_secret,external_name→ camelCase.POST /analyses/:id/client/permissions— body keypermission_id→permissionId.- The messenger broker surface (
POST /messages, pull, ack) —sender_type,sender_id,recipient_type,recipient_id,created_at→ camelCase.
Stored URLs
Bookmarked or emailed links carrying ?filter[realm_id]=…-style query strings stop filtering as intended: the key is silently dropped, so the link returns an unfiltered result set rather than an error. Re-create them.
Audit event diffs
Telemetry events.data.diff records which entity fields changed, keyed by field name. Rows written before the upgrade keep their snake_case keys. They are rendered as-is and never matched by name, so they are left untouched rather than rewritten.
Authup attribute policies
Read this before enabling attribute policies
Nothing here changes behaviour today, and there is nothing to do at upgrade time. It matters the first time an attribute policy is switched on in Authup.
Five server-core permission checks hand a whole Analysis entity to @authup/access as an attribute bag:
await actor.permissionChecker.check({
name: PermissionName.ANALYSIS_UPDATE,
data: new PolicyData({ [BuiltInPolicyType.ATTRIBUTES]: entity }),
});apps/server-core/src/core/entities/analysis/service.ts— create, update, deleteapps/server-core/src/core/services/client-credential/analysis.tsapps/server-core/src/app/modules/database/analysis-client-permission.ts
The attribute policies that match on those key names live in Authup's database, not in this repository, so the rename could not carry them along. As hub is wired today the bag is never read: token introspection delivers policy-free grants, so the evaluator never receives a policy field and every verdict is allow. The mismatch is therefore latent.
It becomes real the moment an attribute policy is attached to an analysis permission. Any policy still written against the old key names stops matching:
| Old attribute key | New attribute key |
|---|---|
realm_id | realmId |
user_id | userId |
project_id | projectId |
master_image_id | masterImageId |
registry_id | registryId |
client_id | clientId |
display_name | displayName |
configuration_locked | configurationLocked |
configuration_*_valid | configuration*Valid |
build_status, build_os, build_hash, … | buildStatus, buildOs, buildHash, … |
distribution_status, distribution_progress | distributionStatus, distributionProgress |
execution_status, execution_progress | executionStatus, executionProgress |
nodes_approved | nodesApproved |
created_at, updated_at | createdAt, updatedAt |
A policy that no longer matches fails in the direction the policy was shaped for. An allowlist-shaped policy (realm_id == X ⇒ allow) stops granting and fails closed — noisy, but safe. A denylist-shaped policy (configuration_locked == true ⇒ deny) stops denying and fails open, which is silent. Audit any deployed attribute policy against the table above before enabling it.