Current behavior and scope
The core gts crate embeds a list of candidate entity ID fields: $id, gtsId, gtsIid, gtsOid, gtsI, gts_id, gts_oid, gts_iid, and id. A similar list selects the type field. GtsEntity::new() uses these conventions to infer metadata from document content.
The immediate scope is entity ingestion through GtsOps and the shared entity model. The REST server delegates registration to GtsOps::add_entity(), and make gts-spec-tests runs the conformance suite against that server. The README describes it as a non-production server for operations and testing.
GtsOps is also used by CLI commands and Rust tests/test helpers, so these callers must be included in the migration. This is not a proposal to require an identifier in every instance payload: direct validation with an explicit type ID remains supported.
Why these heuristics do not belong in the core crate
- Application field names are an integration concern. A domain object's
id, type, or schema may have its own meaning. The core should receive GTS metadata explicitly; a caller or file-format adapter can decide where to obtain it.
- Identity depends on hidden configuration. When multiple candidate fields exist, their order determines the selected ID. Adding a field or changing configuration can change the store key for the same document.
- Conflicts are silently resolved. When
id identifies an instance of type A but type names type B, the ID chain wins, without a metadata-consistency error.
- Storage requirements leak into payload requirements. REST rejects an instance without an ID field even with
validate=false. File loading instead uses the file path or path#index. A source location becomes an instance identifier even though it cannot determine its GTS type.
- The complexity is unnecessary for validation.
GtsStore::validate_payload(type_id, payload) already validates data without extracting identity from it. Alias lists, selection bookkeeping, precedence rules, and file fallbacks add policy to the shared entity model.
Optional helpers with explicit keys can remain in gts; automatic invocation and fallback lists should be removed from core entity construction and registration.
Proposed contract
Use one representation for schemas and instances:
{
"gts_id": "gts.vendor.package.namespace.order.v1~acme.shop.orders.order42.v1",
"content": {
"amount": 100
}
}
- Require a valid
gts_id and a JSON content value.
- Determine entity kind from
gts_id: a type ID ending in ~ identifies a schema; an instance ID identifies a payload and determines its type from the chain. Registration needs no separate type_id.
- Never infer instance identity or type from
content. Its fields are governed by the schema; an ID field is required only if the schema requires it.
- For schemas, require any declared
$id to match the external gts_id after stripping the gts:// prefix.
| Endpoint |
Representation |
POST /entities |
One {gts_id, content} object |
POST /entities/bulk |
An array of these objects |
GET /entities/{gts_id} |
One such object |
GET /entities |
{entities: [{gts_id, content}, ...], count, total} |
Unlike the current metadata-only listing, list entries include content, matching registration and individual retrieval.
Implementation and migration
- Pass
gts_id and content explicitly through GtsOps, entity construction, and store registration. Entity kind must not be re-inferred from content.$schema.
- Keep helpers such as
extract_id(content, key) and extract_type_id(content, key); callers opt in and specify exactly one key.
- Update file-loading adapters to obtain GTS IDs from explicit metadata or an explicitly selected field. Keep paths and array indices as source metadata.
- Preserve validation without registration using an explicit type ID and payload.
- Update affected CLI callers, Rust tests, API documentation, and conformance fixtures. Coordinate the breaking REST contract with
gts-spec-tests; any temporary compatibility extraction belongs in the server/file adapter, outside core entity construction.
Current behavior and scope
The core
gtscrate embeds a list of candidate entity ID fields:$id,gtsId,gtsIid,gtsOid,gtsI,gts_id,gts_oid,gts_iid, andid. A similar list selects the type field.GtsEntity::new()uses these conventions to infer metadata from document content.The immediate scope is entity ingestion through
GtsOpsand the shared entity model. The REST server delegates registration toGtsOps::add_entity(), andmake gts-spec-testsruns the conformance suite against that server. The README describes it as a non-production server for operations and testing.GtsOpsis also used by CLI commands and Rust tests/test helpers, so these callers must be included in the migration. This is not a proposal to require an identifier in every instance payload: direct validation with an explicit type ID remains supported.Why these heuristics do not belong in the core crate
id,type, orschemamay have its own meaning. The core should receive GTS metadata explicitly; a caller or file-format adapter can decide where to obtain it.ididentifies an instance of type A buttypenames type B, the ID chain wins, without a metadata-consistency error.validate=false. File loading instead uses the file path orpath#index. A source location becomes an instance identifier even though it cannot determine its GTS type.GtsStore::validate_payload(type_id, payload)already validates data without extracting identity from it. Alias lists, selection bookkeeping, precedence rules, and file fallbacks add policy to the shared entity model.Optional helpers with explicit keys can remain in
gts; automatic invocation and fallback lists should be removed from core entity construction and registration.Proposed contract
Use one representation for schemas and instances:
{ "gts_id": "gts.vendor.package.namespace.order.v1~acme.shop.orders.order42.v1", "content": { "amount": 100 } }gts_idand a JSONcontentvalue.gts_id: a type ID ending in~identifies a schema; an instance ID identifies a payload and determines its type from the chain. Registration needs no separatetype_id.content. Its fields are governed by the schema; an ID field is required only if the schema requires it.$idto match the externalgts_idafter stripping thegts://prefix.POST /entities{gts_id, content}objectPOST /entities/bulkGET /entities/{gts_id}GET /entities{entities: [{gts_id, content}, ...], count, total}Unlike the current metadata-only listing, list entries include
content, matching registration and individual retrieval.Implementation and migration
gts_idandcontentexplicitly throughGtsOps, entity construction, and store registration. Entity kind must not be re-inferred fromcontent.$schema.extract_id(content, key)andextract_type_id(content, key); callers opt in and specify exactly one key.gts-spec-tests; any temporary compatibility extraction belongs in the server/file adapter, outside core entity construction.