Skip to content

API: Include Parent mgIds in Response

Any API response that exposes an entity's mgId must also expose the mgIds of that entity's parent chain, so that no consumer is ever handed an mgId it cannot locate. A reservation must never travel as a bare RSV592 — it travels with its resource (MG11-SMO33), so a consumer can render MG11-SMO33 RSV592.

Why

An mgId is the human-facing handle for an entity. Some mgIds are self-locating because the generator embeds the parent chain (MgIdService.buildResourceMgIdMG11-SMO33); others are flat global sequences with no positional identity (buildAssetMgId, allocateMgIdRSV592, TXN…, AST…). A flat mgId on its own is an orphan: given RSV592, staff cannot tell which location or studio it belongs to without a second lookup, and any surface that renders it (web, the staff MCP, Sentry alerts, exported reports) produces a dead end.

Today the reservation endpoints return reservation.mgId but select only resource { id, name } / location { id, name } — no mgId on the parents. The data to qualify the handle isn't even in the payload, so downstream consumers fall back to names or emit the orphan. This convention closes that gap at the source: the API hands out enough to build the qualified reference, every time.

The convention

When an API response includes an entity's mgId, it must include the mgId of every ancestor in that entity's parent chain, as raw fields on the nested parent objects.

Two corollaries:

  1. Raw fields, not a pre-joined string. The response carries resource.mgId and location.mgId as discrete values. Joining them into a display string (MG11-SMO33 RSV592) is the display layer's job (web component, Sentry alert text, an assistant's reply), not the API's. The API never invents a ref field.
  2. Self-qualifying mgIds need no extra fields. If an entity's own mgId already embeds its chain (resources, resource groups, access gates, asset placements — see below), exposing that mgId already satisfies the rule. The convention bites for flat / global mgIds, whose parent is a runtime relationship.

Self-qualifying vs flat mgIds

Source of truth: apps/api/src/services/shared/MgIdService.ts.

Kind Examples mgId shape Needs parent fields in response?
Self-qualifying (embeds parents) Location MG11, Resource MG11-SMO33, ResourceGroup, AccessGate, AssetPlacement parent chain baked into the string No — the mgId is already locatable
Flat / global (no positional identity) Reservation RSV592, Transaction, Asset AST…, Credit, Job, WaitlistEntry bare prefix + global sequence Yes — include the parent chain's mgIds

This mirrors the existing generator philosophy: fixed-position entities embed parents; movable/relational entities stay flat. Reservations are deliberately flat because a reservation can be moved to a different resource/location (so embedding the parent into the stored id would go stale). The qualified handle is therefore assembled at read time, from the relationship as it stands now.

Parent chain per flat entity

The chain is resolved through the entity's current relationships, outermost ancestor first:

  • Reservationresource (→ location). Expose resource.mgId (self-qualifying, already carries the location) and, for convenience, location.mgId. Display ref: <resource.mgId> <reservation.mgId>.
  • Payment → whichever owner it is attached to (reservation, creditBalance, waitlistEntry, or organization). Expose that owner's mgId chain.
  • Transaction / Job / Credit / WaitlistEntry → expose the mgId chain of the entity the row is about, when one applies.

When a flat entity has no parent in a given context (e.g. an org-level payment), there is no chain to add — the rule is satisfied vacuously.

Display formatting

Joining the chain into a single human string is a display concern with one shared helper, so the format is identical everywhere:

  • Reservation: <resource.mgId> <reservation.mgId>MG11-SMO33 RSV592.
  • The resource mgId already embeds the location, so a reservation ref is two tokens, not three. Do not prepend the location mgId separately (it would double-print: MG11 MG11-SMO33 …).

Consumers of the formatter: the staff web UI, the reservation-health engine's Sentry findings, ops scripts, and assistant replies (the global CLAUDE.md "never return an orphan mgId" rule is the display-side expression of this convention).

Constraints

  • Additive and backward-compatible. Adding mgId to nested parent objects is a non-breaking response change. No existing field is removed or renamed.
  • No schema changes. All parent mgIds already exist on locations, resources, etc. This is selection + serialization only.
  • Read path only. This convention is about what responses expose. It does not change how mgIds are generated or stored.
  • Respect existing access control. Parent mgIds inherit the same visibility as the rest of the entity; do not expose a parent the caller couldn't already see.

Scope

Two layers:

  1. Data availability (this convention). Retrofit the existing endpoints that expose a flat entity's mgId without its parents — starting with the reservation endpoints and the reservation-health engine's finding context — and make the rule the default for all new endpoints.
  2. Display (consuming the data). A single shared formatter, adopted by web, Sentry alert text, and scripts. Tracked in the implementation plan.

Enforcement

  • New endpoints that serialize a flat entity's mgId must include the parent chain; this is a review checklist item and belongs in the API response-envelope docs (docs/engineering/architecture/api/).
  • The reservation-health engine (docs/features/reservation-health/) is the first internal consumer: its findings currently emit bare RSV### into Sentry and must carry resource.mgId.

Relationship to other work

  • Staff MCP (docs/features/staff-mcp/) — the reservation tools don't expose mgIds yet (they return UUID + names); when they do, they inherit this convention for free by wrapping the corrected API responses.
  • Reservation Health (docs/features/reservation-health/) — first retrofit target for the finding context.