diff --git a/.github/workflows/buf.yml b/.github/workflows/buf.yml index 399c954..2bd3ee6 100644 --- a/.github/workflows/buf.yml +++ b/.github/workflows/buf.yml @@ -38,4 +38,4 @@ jobs: with: push: false breaking: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'buf:skip:breaking') }} - breaking_against: ${{ github.event.repository.clone_url }}#branch=main,ref=HEAD~1 + breaking_against: ${{ github.event.repository.clone_url }}#branch=main diff --git a/docs/explanation/actor-identity.md b/docs/explanation/actor-identity.md index 3a1aef5..c3b09de 100644 --- a/docs/explanation/actor-identity.md +++ b/docs/explanation/actor-identity.md @@ -6,20 +6,44 @@ At application boundaries, the caller may be represented as an authenticated pri ## Choosing Actor or ActorId -Prefer `Actor` for shared event metadata and any field where consumers may need to distinguish users, services, systems, schedulers, integrations, or other actor classes. This is the safer default for reusable event envelopes because the actor taxonomy remains part of the field contract. +Use `ActorId` when the actor can be represented by one canonical identifier. The value may be a prefixed id or resource-name-like string, such as `users/usr_123`, `services/scheduler`, `systems/cron`, or `integrations/github`. -Use `ActorId` only when the surrounding event contract already fixes the actor class and the string identifier alone is the durable domain fact. Do not use `ActorId` just to avoid the `type` field in JSON. +Use `Actor` when consumers need the actor class and local id as separate fields. This is useful when a service wants to keep the actor taxonomy explicit instead of encoding it in one reference value. -For example, common event metadata should usually use `Actor`: +For example, generic event metadata can use `ActorId` when the platform standardizes self-describing actor identifiers: ```protobuf message EventMetadata { - trogon.actor.v1alpha1.Actor actor = 1; + trogon.actor.v1alpha1.ActorId actor = 1; + trogon.actor.v1alpha1.ActorId on_behalf_of = 2; } ``` The protobuf JSON shape is object-based: +```json +{ + "actor": { + "value": "users/usr_123" + }, + "onBehalfOf": { + "value": "services/support-console" + } +} +``` + +In that contract, `ActorId` already owns the actor-reference concept. A separate `ActorRef` type would describe the same concept with a different name. + +The field inside `ActorId` is named `value` because the message itself carries the domain role. Renaming it to `ref` would make JSON and generated APIs churn without adding a separate concept. + +Use `Actor` when the split is part of the contract: + +```protobuf +message EventMetadata { + trogon.actor.v1alpha1.Actor actor = 1; +} +``` + ```json { "actor": { @@ -33,7 +57,7 @@ The protobuf JSON shape is object-based: `id` is an opaque identifier scoped by `type`. It is not display text, and consumers should compare actors by both fields. -An event-specific field can use `ActorId` when the domain fact is only the identifier and the actor class is already implied: +An event-specific field can also use `ActorId` when the domain fact is the canonical actor identifier: ```protobuf message AccountCreated { @@ -46,12 +70,12 @@ The protobuf JSON shape for `ActorId` is: ```json { "createdBy": { - "value": "usr_123" + "value": "users/usr_123" } } ``` -Changing a field from `ActorId` to `Actor` later changes the protobuf and JSON shape for that field. Choose `Actor` up front when a later type distinction would belong to the same event contract. +Changing a field from `ActorId` to `Actor` later changes the protobuf and JSON shape for that field. Choose `Actor` up front when consumers need separate actor fields rather than one canonical actor identifier. ## Object ID Values @@ -61,15 +85,14 @@ Use `Actor` or `ActorId` when the field answers who caused the event: ```protobuf message AccountCreated { - trogon.actor.v1alpha1.Actor created_by = 1; + trogon.actor.v1alpha1.ActorId created_by = 1; } ``` ```json { "createdBy": { - "type": "user", - "id": "usr_123" + "value": "users/usr_123" } } ``` @@ -79,11 +102,11 @@ Do not use `ActorId` for fields that identify the object being created, updated, ```protobuf message AccountCreated { string account_id = 1; - trogon.actor.v1alpha1.Actor created_by = 2; + trogon.actor.v1alpha1.ActorId created_by = 2; } ``` -If the event contract already implies the actor type, an object-id string can be carried by `ActorId`: +If the event contract already implies the actor namespace, a shorter id can still be carried by `ActorId`: ```protobuf message UserProfileUpdated { @@ -91,4 +114,4 @@ message UserProfileUpdated { } ``` -In that case, the value is still an actor identifier in the event, not a general object reference. +In that case, the value is still an actor identifier in the event, not a general object reference. Prefer self-describing values in shared event envelopes so replay, projection, and audit consumers do not need external context to interpret the actor. diff --git a/proto/trogon/actor/v1alpha1/actor.proto b/proto/trogon/actor/v1alpha1/actor.proto index a4a595d..d3b7912 100644 --- a/proto/trogon/actor/v1alpha1/actor.proto +++ b/proto/trogon/actor/v1alpha1/actor.proto @@ -5,16 +5,17 @@ import "elixirpb.proto"; option (elixirpb.file).module_prefix = "TrogonProto.Actor.V1Alpha1"; -// ActorId identifies a domain actor when the actor type is already implied by -// the surrounding contract. +// ActorId is the stable identifier for a domain actor. // -// Use Actor when event consumers need both type and id. Use ActorId only when -// the identifier alone is the durable domain contract. +// The value should be self-describing within the actor namespace, such as a +// prefixed id or resource-name-like identifier. Use Actor when consumers need the +// actor class and local id as separate fields. message ActorId { - // value is a stable opaque actor identifier. + // value is the canonical actor identifier. // - // This value is not display text and is comparable only within the domain - // boundary defined by the surrounding contract. + // This value is not display text. Consumers should compare the complete + // value and should not rely on parsing unless the actor namespace documents + // that contract. string value = 1; }