Files
probo/contrib/claude/gid.md
Bryan Frimin ab52dc0a34 Clean agent rules
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-04-19 11:42:50 +02:00

2.9 KiB
Raw Permalink Blame History

GID — Global Identifiers (pkg/gid)

Every entity ID in the system is a 24-byte tenant-scoped GID, serialized as base64url in the database, JSON, and API surfaces.

GID layout (24 bytes / 192 bits)

Bytes Size Content
07 8 bytes Tenant ID
89 2 bytes Entity type (uint16)
1017 8 bytes Timestamp (milliseconds since epoch)
1823 6 bytes Random data

Creating a GID

GIDs are created in the service layer (e.g. pkg/probo/*_service.go), not in coredata Insert methods. The entity type constant comes from pkg/coredata/entity_type_reg.go:

assetID := gid.New(s.svc.scope.GetTenantID(), coredata.AssetEntityType)

asset := &coredata.Asset{
    ID:             assetID,
    OrganizationID: req.OrganizationID,
    Name:           req.Name,
    CreatedAt:      now,
    UpdatedAt:      now,
}

err := asset.Insert(ctx, conn, s.svc.scope)

gid.New panics on random source failure (should never happen). Use gid.NewGID if you need the error.

Extracting fields

id.TenantID()    // TenantID (first 8 bytes)
id.EntityType()  // uint16 (bytes 89)
id.Timestamp()   // time.Time (bytes 1017)

Parsing and serialization

  • gid.ParseGID(encoded) — base64url string to GID
  • gid.String() — GID to base64url string
  • Implements sql.Scanner, driver.Valuer, MarshalText, UnmarshalText
  • gid.Nil — zero-value GID

TenantID

TenantID is an 8-byte type with its own layout:

Bytes Size Content
02 3 bytes Machine ID (random per process)
35 3 bytes Timestamp (truncated Unix seconds)
67 2 bytes Atomic counter

Create with gid.NewTenantID(). Check with tenantID.IsValid() (non-nil). Same serialization interfaces as GID (base64url, SQL scanner/valuer).

Entity type registry

All entity type constants live in pkg/coredata/entity_type_reg.go as sequential uint16 values:

const (
    OrganizationEntityType uint16 = 0
    FrameworkEntityType    uint16 = 1
    MeasureEntityType      uint16 = 2
    // ...
    _                      uint16 = 8  // PeopleEntityType - removed
    // ...
)

Never reuse removed type numbers. Use _ placeholders with a comment noting what was removed. New types get the next available number.

NewEntityFromID(id gid.GID) (any, bool) switches on id.EntityType() and returns a pointer to the concrete coredata struct with ID: id set, or nil, false for unknown types. Add a case here when registering a new entity type.

  1. Add FooEntityType uint16 = N in the const block in entity_type_reg.go (next sequential number)
  2. Add a case FooEntityType in NewEntityFromID returning &Foo{ID: id}, true
  3. In the service Create method, call gid.New(scope.GetTenantID(), coredata.FooEntityType) to generate the ID before Insert