84
contrib/claude/gid.md
Normal file
84
contrib/claude/gid.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# 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 |
|
||||
|-------|------|---------|
|
||||
| 0–7 | 8 bytes | Tenant ID |
|
||||
| 8–9 | 2 bytes | Entity type (`uint16`) |
|
||||
| 10–17 | 8 bytes | Timestamp (milliseconds since epoch) |
|
||||
| 18–23 | 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`:
|
||||
|
||||
```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
|
||||
|
||||
```go
|
||||
id.TenantID() // TenantID (first 8 bytes)
|
||||
id.EntityType() // uint16 (bytes 8–9)
|
||||
id.Timestamp() // time.Time (bytes 10–17)
|
||||
```
|
||||
|
||||
## 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 |
|
||||
|-------|------|---------|
|
||||
| 0–2 | 3 bytes | Machine ID (random per process) |
|
||||
| 3–5 | 3 bytes | Timestamp (truncated Unix seconds) |
|
||||
| 6–7 | 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:
|
||||
|
||||
```go
|
||||
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.
|
||||
|
||||
## New entity checklist (GID-related steps)
|
||||
|
||||
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`
|
||||
Reference in New Issue
Block a user